Skip to content

Identity verification

Markdown

identify(userId:) ties a device to one of your users. With a signed token from your server, their history follows them to every device. Without one, Concierge can’t know the userId is real (your app key ships inside the app, so anyone can send any id), so an unverified identify stays on the device that made it.

New apps require verification by default.

  1. Copy the app’s identity secret (ids_...) from the dashboard into your server’s environment. It never ships in your app.
  2. Your server signs an HS256 JWT whose sub (or userId claim) equals the user’s id. exp is required and can be at most 30 days away.
  3. The app passes that token to identify.
  4. Keep Require verified users on (the default for new apps). Identify without a token answers 401 identity_verification_required, and identified customers must be verified to send. Signed-out devices that never called identify can still chat unless you turn off Signed-out chat.

No library needed. Node:

import { createHmac } from 'node:crypto';
const b64 = (value) => Buffer.from(JSON.stringify(value)).toString('base64url');
export function conciergeToken(userId) {
const header = b64({ alg: 'HS256', typ: 'JWT' });
const payload = b64({ sub: userId, exp: Math.floor(Date.now() / 1000) + 60 * 60 });
const signature = createHmac('sha256', process.env.CONCIERGE_IDENTITY_SECRET)
.update(`${header}.${payload}`)
.digest('base64url');
return `${header}.${payload}.${signature}`;
}

Any JWT library works too (jose, PyJWT, golang-jwt, firebase/php-jwt): algorithm HS256, key = the identity secret, sub = your user id.

// iOS
Concierge.identify(userId: user.id, token: try await api.conciergeToken())
// Android
Concierge.identify(userId = user.id, token = jwtFromYourServer)

The iOS SDK keeps the token in memory only, so fetch a fresh one every launch. If the server rejects it, iOS emits .identityRejected(error) through Concierge.onEvent.

  • A userId that was ever verified can only be claimed again with a valid token. Turning verification on later doesn’t leave old users exposed.
  • Verified: an anonymous device that identifies as a new userId is upgraded in place. One that identifies as an existing user is moved to that customer, and its anonymous messages are appended to the user’s conversation.
  • Unverified (verification off, no token): the claim belongs to that one device. Another device sending the same userId gets its own separate customer, never the first one’s history or profile. The inbox shows these as Unverified with the claimed id. Sign the user in with a token later and the device’s history joins their verified account.
  • logout() starts a fresh anonymous customer on that device.
  • The verified user id also drives the store account token that links App Store and Play purchases to the customer (refund desk, receipts, renewal heads ups). Unverified ids never match. See Link purchases to customers.

Rotate in the dashboard (or POST /v1/apps/:appId/identity-secret/rotate) returns a new secret, and only that response and the setup screen ever show it. The old secret keeps verifying tokens for 24 hours, and the dashboard shows both with the time the old one expires, so you can deploy the new secret to your server without breaking sign-ins. Rotating again inside those 24 hours retires the oldest secret right away.