Identity endpoint
On the Security tab, under Customer identity, set Your identity endpoint to an address on the same site that embeds the agent. The widget calls it from the visitor’s browser, so the request arrives with your existing login cookies and you can answer from your own session.
Return a short-lived token for a signed-in customer, or an empty object for a visitor with no account.
{ "identity_token": "<short-lived-jwt>" }{}A working implementation in Next.js. Point currentUser at your real server-side session lookup, install jose, and keep the secret on the server. The 60-second token leaves headroom under the 120-second limit.
import { SignJWT } from 'jose';
import { NextResponse } from 'next/server';
import { currentUser } from '@/lib/auth';
const noStore = {
'cache-control': 'no-store, no-cache, must-revalidate, max-age=0',
pragma: 'no-cache',
};
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export async function GET() {
const user = await currentUser();
if (!user) return NextResponse.json({}, { headers: noStore });
const rawSecret = process.env.DUVI_IDENTITY_SECRET;
if (!rawSecret) throw new Error('DUVI_IDENTITY_SECRET is required');
const secret = new TextEncoder().encode(rawSecret);
const identityToken = await new SignJWT({ email: user.email })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(user.id)
.setAudience('duvi')
.setIssuedAt()
.setExpirationTime('60s')
.sign(secret);
return NextResponse.json(
{ identity_token: identityToken },
{ headers: noStore },
);
}Token contract
{
"aud": "duvi",
"sub": "customer_4f91",
"exp": 1788253500,
"plan": "pro"
}| Requirement | Contract |
|---|---|
| Algorithm | HS256 only |
| Audience | aud must be duvi |
| Customer | sub is required and must remain stable for the same person |
| Expiry | exp is required; maximum lifetime is 120 seconds |
| Clock tolerance | 30 seconds either side |
The widget refreshes identity 15 seconds before expiry, so a short lifetime costs you nothing. Duvi keeps any other claims you add and can pass them into tool calls, apart from the reserved JWT claims.
Signing keys
A signing key is the shared secret your endpoint signs with. You create it, so it is whatever value you already keep on your server. Select Add key, give it a name and paste the value. Duvi stores it encrypted and never shows it again.
An agent can hold several keys, and exactly one is marked In use. Duvi verifies tokens against that one.
Add the new key first
Add it alongside the old one. Nothing changes yet, because the old key is still in use.
Start signing with the new value
Deploy the new secret to your own server.
Switch which key is in use
Select Use this key on the new one. The old key keeps working right up until you switch, so no customer is rejected part way through the change.
Require a sign-in
By default anyone may open a conversation, and Duvi uses verified identity when it happens to be available. Switching Access to Only signed-in visitors refuses anyone your endpoint cannot identify, before any model runs.
| Access | Behaviour |
|---|---|
| Anyone | Visitors may start anonymously. Verified identity is used when available. |
| Only signed-in visitors | An unidentified visitor is refused before any model call. |
The second option becomes selectable only once both an endpoint and a signing key exist. If identity later breaks while it is selected, every visitor is refused and Studio says so at the top of the tab.
Use claims in tools
On any tool field that decides whose data comes back, set the value source to From the signed-in customer. The agent never sees that value and cannot invent it.
Never use Ask the customer for an account id, an order reference tied to an account, or anything else that selects a record. That value is whatever the person on the other end typed. See Value sources.
To have your own API accept the customer rather than the agent, connect the tool to an integration that signs in as them. That is set up under Tools and covered in Call your API as the customer, including the option to accept a verified phone number on calls.
Test the setup
Signed out
Confirm the endpoint returns
{}and the agent applies your anonymous access rule.Signed in
Confirm the token carries the exact audience, a stable subject and an expiry no more than 120 seconds away.
Private tool
Call a tool with a signed-in customer field and verify your API receives the signed claim, not something typed in chat.
Failure
Break the signature deliberately. A protected conversation must refuse, and a token exchange must stop before your endpoint is called.
For symptoms and their causes, see Troubleshooting.