Identity Verification

Identity verification proves that SDK identify calls came from your application. Your server signs identity data with a shared secret, and Aimdoc verifies the signature before trusting it.

How it works

  1. Your backend computes an HMAC-SHA256 signature using your Aimdoc identity verification secret.
  2. Your frontend passes that signature as user_hash to aimdoc.identify.
  3. Aimdoc normalizes the submitted identity fields, recomputes the signature, and rejects the request if any canonical signed value was changed.

User-only identifies use the legacy signature over the user's external ID and normalized email. Identifies containing an account object use the v2 canonical payload, which also covers the account external ID, name, and domain.

Configure a signing secret

In the Aimdoc dashboard:

  1. Open Integrations.
  2. Select Identity Verification.
  3. Choose Generate signing secret.
  4. Store the secret in your backend's secret manager or server environment.

Treat this value like an API credential. Anyone with the secret can generate valid identity assertions for your organization.

Sign identify calls

For a user-only identify, sign "{external_id}:{normalized_email}":

// Server (Node.js)
import { createHmac } from 'crypto'

const userHash = createHmac(
  'sha256',
  process.env.AIMDOC_IDENTITY_SECRET,
)
  .update(`${user.id}:${user.email.trim().toLowerCase()}`)
  .digest('hex')

Pass the server-generated value to the browser:

aimdoc.identify({
  external_id: user.id,
  email: user.email,
  user_hash: userHashFromServer,
})

Account identity affects shared account records, so every identify containing account requires a v2 signature—even before organization-wide enforcement is enabled. The v2 hash must cover the complete canonical account payload.

// Server (Node.js)
import { createHmac } from 'crypto'

const canonicalizeDomain = (value = '') => {
  const rawValue = value.trim()
  if (!rawValue) return ''
  const url = new URL(
    rawValue.includes('://') ? rawValue : `https://${rawValue}`,
  )
  return url.hostname
    .toLowerCase()
    .replace(/\.+$/, '')
    .replace(/^www\./, '')
}

const signedPayload = JSON.stringify({
  account: {
    domain: canonicalizeDomain(account.domain),
    external_id: account.external_id.trim(),
    name: (account.name || '').trim(),
  },
  email: user.email.trim().toLowerCase(),
  external_id: user.id.trim(),
  version: 2,
})

const userHash = createHmac(
  'sha256',
  process.env.AIMDOC_IDENTITY_SECRET,
)
  .update(signedPayload)
  .digest('hex')

The key order shown above is part of the canonical representation. Send the same values and the server-generated hash to the browser:

aimdoc.identify({
  external_id: user.id,
  email: user.email,
  account: {
    external_id: account.external_id,
    name: account.name,
    domain: account.domain,
  },
  user_hash: userHashFromServer,
})

See SDKs for the complete account identity behavior and SDK method usage.

Enable enforcement

After all of your applications send signed user-only identifies, enable Enforce identity verification in the dashboard. Aimdoc will then reject every unsigned identify call.

Roll this out in order:

  1. Generate and securely store the secret.
  2. Deploy server-side signature generation.
  3. Verify your application sends valid hashes.
  4. Enable enforcement.

Invalid hashes are always rejected, regardless of the enforcement setting.

Rotate the secret

Choose Rotate in the Identity Verification settings when you need to replace a secret. Rotation immediately invalidates signatures generated with the previous secret.

Deploy the new secret to your backend promptly after rotation. Identify calls using the old secret will fail until your server generates hashes with the new one.

Was this page helpful?