> ## Documentation Index
> Fetch the complete documentation index at: https://docs.custral.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget visitor identity

> Vouch for who a chat-widget visitor is, so their conversations attach to the right person.

By default the chat widget only knows what a visitor types into it. That is fine
for capturing a lead and not enough for anything else. The email in a chat box
is a string anyone can type on a public page, so it cannot be used to attach a
conversation to the right person or to show them their own history.

If your users are already signed in to your product, you can tell us who they
are. Your **server** mints a short-lived token and your page hands it to the
widget.

<Warning>
  Mint the token on your server, never in the browser. It authenticates with your
  secret key, and a token minted client-side would let anyone assert anyone's
  identity.
</Warning>

## Mint a token

<CodeGroup>
  ```ts Node (SDK) theme={null}
  const {token} = await custral.widget.createVisitorToken({
    externalId: user.id,        // your own id for this person, required
    email: user.email,
    name: user.fullName,
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://api.custral.com/v1/widget/visitor-tokens \
    -H "Authorization: Bearer sk_live_…" \
    -H "Content-Type: application/json" \
    -d '{"externalId": "usr_8812", "email": "marcus@northlane.io", "name": "Marcus Webb"}'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "token": "wit_9f2c1b7a4e6d0a83c5b1f7e2d4a6c8b0f3e5d7a9c1b3e5f7",
    "expiresAt": "2026-08-22T15:11:44Z"
  }
  ```
</ResponseExample>

Requires the `widget:identify` scope. The raw token is returned here and nowhere
else. We store only its hash.

## Hand it to the widget

```tsx theme={null}
<CustralChatWidget publishableKey="pk_live_…" identityToken={token} />
```

That is the whole integration. A visitor who arrives with a valid token skips the
pre-chat form, is matched to the same person across their devices rather than
per-browser, and their conversation attaches to their record in your CRM.

## Parameters

<ParamField body="externalId" type="string" required>
  Your own identifier for the signed-in user. This is the identity, everything
  else is optional detail.
</ParamField>

<ParamField body="email" type="string">
  Their email address, if you hold one.
</ParamField>

<ParamField body="name" type="string">
  Their display name, if you hold one.
</ParamField>

<ParamField body="phone" type="string">
  Their phone number, if you hold one.
</ParamField>

<ParamField body="expiresIn" type="integer" default="600">
  Lifetime in seconds. Clamped to a maximum of 3600.
</ParamField>

## Notes

* **Mint per page render.** Tokens are short-lived by design. They may be
  presented more than once inside their window, so a single-page app re-initing
  the widget does not need a fresh one.
* **Nothing breaks without it.** A widget with no `identityToken` behaves exactly
  as it always has: the pre-chat form runs and the visitor is treated as
  unidentified. An expired token is the same, never an error, never a blank
  widget.
* **A vouched-for visitor cannot be overwritten.** Once we hold a verified
  identity for someone, the widget's own public identify call will not change
  their details.


## Related topics

- [Widget](/dev/webhooks/events/widget.md)
- [widget.visitor.identified](/dev/webhooks/events/widget-visitor-identified.md)
- [Install the widget](/comms/chat/widget-install.md)
- [Visitors](/comms/chat/visitors.md)
- [React SDK](/dev/sdks/react.md)
