- agents see a verified contact (a ✓ next to the name) with the name, email and attributes your app sent;
- agents see the customer’s company and their team’s other chats, and the AI knows which company they’re from;
- the customer’s chats follow them across browsers and devices;
- the AI’s actions can act on their account with
{user.id},{user.email},{user.name}and{user.<attribute>}, which come from your token, never from the chat; - actions can call your API with the customer’s own key through
{user.secrets.NAME}.
Without a token
If you only want agents to see who they’re talking to, skip the server part and pass the user’s id, name and email from the page:identify: it labels the visitor, nothing more. Agents see the name and email in the inbox, on Contacts and on the Visitors list, and the inbox’s customer card says Not verified, since any visitor could call it with any email from the browser console. Email replies go to that address.
None of the list above applies. Without a token:
A few rules:
- A malformed email is dropped. A name is kept to one line of up to 120 characters, an id to 200 characters (a number becomes text).
- A verified contact keeps the details its token gave. A later call without a token doesn’t change them.
- If the same browser later says a different email or id, it starts a new contact. On a shared computer, one person’s chats never show under the next person’s name. Still call
JunDesk.logout()on sign-out.
Same person on several browsers
Each browser is its own contact, but contacts that share an email or a user id are linked into one person on the desk. That covers emails fromidentify(), from the chat’s email ask and from an agent’s edit, plus ids from identify() and from a verified token’s sub. The Contacts list shows them as one row (“2 browsers”) with all their chats. The inbox’s customer card and the person’s page list every chat and say Same person: N other browsers, matched by email or user ID (not verified).
That’s for your team only. Each browser’s chat still shows only the chats it started there. The AI doesn’t treat the visitor as signed in or reach their other chats. Two verified users are never linked, even with the same email, because your app says they’re different people.
When the match is wrong, or misses someone, fix it on the person’s page under Contacts: the Same person card lists each linked browser with Unlink, and Link finds another contact by name or email. After that, matching by email or user id leaves those contacts where you put them. Linking two signed-in users is refused. On Visitors, a person’s browsers sit together, tagged Same person.
From PostHog
If your app already callsposthog.identify(), add data-identify-from="posthog" to the script tag and skip the call above:
distinct_id, plus name and email from the person properties you pass to identify()) and labels the visitor with them, again whenever identify() runs. It’s the same label as identify({ id, name, email }): not verified. A visitor already signed in with a token is left alone. When your app calls posthog.reset() on sign-out, the widget logs the visitor out too, so the next person on that computer starts fresh.
To move up to a verified contact later, add the token to the same call: JunDesk.identify({ id, name, email, token }). The token wins, and name and email then come from its claims.
How it works
1
Create the identity secret
Go to Chat widget → Behaviour and press Create identity secret on the Identify signed-in customers card. Only owners and admins see this card.The secret starts with
jis_. Use Show and Copy to get it, and store it on your server, for example as JUN_IDENTITY_SECRET. Never put it in front-end code.2
3
Pass the token to the widget
Either put it on the snippet:or call The snippet loads with
JunDesk.identify() once your app knows the user (for example after sign-in):defer, so early in a page’s life window.JunDesk may not exist yet. If your app can sign in before the loader has run, wait for its jundesk:ready event:4
Call logout when they sign out
Claims
The desk allows 60 seconds of clock skew on
exp and nbf.
Attributes
- Up to 50 keys. Each key must start with a letter or
_, use only letters, digits and_, and be at most 40 characters. Other keys are skipped. - Values may be strings (cut to 500 characters), finite numbers or booleans. Anything else (objects, lists,
null) is skipped. - Each verified token replaces the contact’s attributes: your app is the source of truth.
nameandemailare updated when present and kept when left out.
Company
For B2B apps, say which company the customer belongs to:- The desk keeps one company per
idin the workspace. Its name and attributes are replaced by the latest token that names it: your app is the source of truth. - A token without
companyunlinks the customer from their company. - Agents see a Company section in the conversation’s side panel (name, company id, attributes, how many of its people have signed in) and Their team’s conversations: the latest 10 chats from the company’s other people. See Contact details.
- The AI sees the company’s name and attributes with the customer’s, like
company: Acme (plan: enterprise, seats: 40).
Actions can’t use
{company.*} placeholders yet. To send a company fact to your API, put it in the customer’s attributes too and use {user.<attribute>}.Rejected tokens
A token is rejected, and the visitor stays anonymous, when:
The chat frame logs these as
[Jun] identify failed: …. Bad attributes (not an object), a company that isn’t an object or has no id, or bad secrets also reject the token.
Server examples
check_identity_token: it answers exactly as the chat would. Don’t paste tokens into third-party decoder sites: a token with a secrets claim carries the customer’s own key.
Then hand the token to the page, for example by rendering it into the snippet’s data-user-token attribute, or by returning it from an endpoint your front end calls before JunDesk.identify(token).
Merging rules
What happens when a browser identifies depends on who it was before:
Identified contacts never merge with each other, so one user can never inherit another user’s history. Merged conversations update in agents’ inboxes right away.
Signing out
CallJunDesk.logout() when the customer signs out of your app. It:
- forgets the chat identity in this browser, so the next person starts fresh, with no access to the last person’s conversations;
- drops the customer’s user secrets on the desk right away;
- starts a new visitor session on the Visitors list.
JunDesk.identify(null) also forgets the chat identity in the frame, but doesn’t start a new session. Use logout().
User secrets
Some APIs only accept the customer’s own key. Put it in the token’ssecrets claim:
{user.secrets.API_TOKEN} (Actions).
- Names are UPPER_SNAKE_CASE: a capital letter, then capitals, digits and
_, up to 64 characters. - Up to 10 secrets per token, each a non-empty string of up to 4,096 characters. A bad name or value rejects the whole token.
- The desk keeps them only until the token’s
exp, or untilJunDesk.logout(). They’re kept apart from the contact: never stored with it, never shown to agents, never put in the AI’s prompt, and masked in action responses. - To keep them past
exp, sign a fresh token and callJunDesk.identify()again.
Rotate or turn off the secret
On the Identify signed-in customers card:- Rotate secret creates a new one. Every token signed with the old secret stops working at once, so update your server first or right after.
- Turn off removes the secret. Everyone becomes anonymous, and identify calls fail with “Identity verification isn’t set up for this desk”.
set_identity does the same with turn: "rotate" or turn: "off", and refuses both until it’s called with confirm: true.
Actions
Use
{user.id} and {user.secrets.NAME} in your API calls.JavaScript API
identify, logout and the rest.