Identity Verification
Identity verification lets your website tell Verly who the current logged-in user is. Once verified, the widget can personalize conversations, resolve the right contact record, and fill custom action variables with trusted user data.
What Identity Verification Enables
- Personalized conversations so the agent can greet users by name and respond with account-aware context.
- Authenticated actions using template variables like
{{contact.externalId}}and{{contact.metadata.plan}}. - Automatic contact sync by creating or updating the Verly contact from the verified JWT.
- Safer shared-device behavior when you reset identity on logout.
Before You Start
- Your website already has the Verly widget embed script installed.
- Identity verification is enabled for the chatbot in Deploy > Identity.
- Your backend can sign JWTs with
HS256. - Your identity secret is stored only on the server.
How It Works
- A user signs in to your app.
- Your backend signs a JWT with the chatbot's identity secret.
- Your frontend calls
window.conversly("identify", { token, ...publicMeta }). - The widget sends the JWT and public metadata to Verly with each request.
- Verly verifies the token, resolves the contact, and injects trusted identity data into the session and custom actions.
- On logout, your app calls
window.conversly("resetUser")to clear the stored identity and session references.
Step 1: Generate Your Identity Secret
- Open your chatbot in the Verly dashboard.
- Go to Deploy.
- Open the Identity tab.
- Click Generate Secret.
- Copy the generated secret and store it in a backend environment variable.
The generated secret starts with iv_. Rotating the secret immediately invalidates previously issued JWTs.
Step 2: Sign a JWT on Your Server
Use any JWT library that supports HS256. The token must include either sub or user_id, plus an exp timestamp.
const jwt = require("jsonwebtoken");
const secret = process.env.CONVERSLY_IDENTITY_SECRET;
function createConverslyIdentityToken(user) {
return jwt.sign(
{
sub: user.id,
email: user.email,
name: user.name,
phonenumber: user.phone,
custom_attributes: {
plan: user.plan,
company_id: user.companyId,
support_tier: user.supportTier,
},
exp: Math.floor(Date.now() / 1000) + 60 * 60,
},
secret,
{ algorithm: "HS256" }
);
}
import jwt
import os
import time
secret = os.environ["CONVERSLY_IDENTITY_SECRET"]
def create_conversly_identity_token(user):
payload = {
"sub": user["id"],
"email": user["email"],
"name": user["name"],
"phonenumber": user.get("phone"),
"custom_attributes": {
"plan": user.get("plan"),
"company_id": user.get("company_id"),
"support_tier": user.get("support_tier"),
},
"exp": int(time.time()) + 3600,
}
return jwt.encode(payload, secret, algorithm="HS256")
JWT Claims Reference
| Claim | Required | Purpose |
|---|---|---|
sub or user_id | Yes | Stable unique user identifier. Stored as the contact's external ID. |
exp | Yes | Expiration timestamp in Unix seconds. Recommended: 1 hour. Maximum: 24 hours. |
email | No | Stored on the contact and available to actions. |
name | No | Stored on the contact and available to the AI session. |
phonenumber | No | Stored on the contact and available to actions. |
custom_attributes | No | Arbitrary key-value data merged into contact metadata. Max serialized size: 4 KB. |
sub or user_id. Avoid identifiers like email or username if those values can change.Step 3: Identify the User in the Widget
The widget API is exposed as window.conversly(...).
Recommended: identify after login
const response = await fetch("/api/conversly/identity-token");
const { token, user } = await response.json();
window.conversly("identify", {
token,
name: user.firstName,
plan: user.plan,
});
Anything passed outside the JWT, such as name or plan, is treated as public session metadata. It can help the agent personalize replies, but it is not persisted to the contact record and does not power {{contact.*}} variables in actions.
Alternative: pre-load identity before the widget initializes
<script>
window.converslyUserConfig = {
token: identityTokenFromYourBackend,
name: currentUser.firstName,
plan: currentUser.plan
};
</script>
<script
src="https://widget.verlyai.xyz/embed.js"
data-chatbot-id="bot_123"
></script>
Step 4: Reset Identity on Logout
window.conversly("resetUser");
This clears the stored identity token plus the local contact and conversation references. Always do this on logout, especially on shared browsers.
What Verly Stores and What the AI Can See
| Data source | Visible to the AI in-session? | Available in custom actions? | Persisted to contact? |
|---|---|---|---|
Public metadata passed to identify() | Yes | No | No |
Standard JWT fields like sub, email, name, phonenumber | Yes | Yes, via {{contact.*}} | Yes |
custom_attributes inside the JWT | Yes | Yes, via {{contact.metadata.*}} | Yes |
Template variables are resolved server-side from verified data. The model can use identity context in conversation, but it cannot forge {{contact.*}} values sent to your APIs.
Using Identity in Custom Actions
Once a user is verified, these variables are available in custom actions automatically:
| Variable | Value |
|---|---|
{{contact.externalId}} | The verified sub or user_id |
{{contact.email}} | The contact email from the JWT |
{{contact.name}} | The contact name from the JWT |
{{contact.phone}} | The contact phone number from the JWT |
{{contact.id}} | The internal Verly contact ID |
{{contact.metadata.<key>}} | Any value from custom_attributes |
Example request configuration:
GET https://api.yourapp.com/companies/{{contact.metadata.company_id}}/users/{{contact.externalId}}/orders
X-Support-Tier: {{contact.metadata.support_tier}}
If a verified user asks, "Show me my recent orders," Verly can call your action with those placeholders already filled from the verified identity.
Contact Behavior
- First verified session: Verly creates a contact with the external ID from the JWT.
- Returning verified user: Verly finds the existing contact by external ID and updates provided fields.
- Anonymous to verified: An existing visitor contact can be upgraded when identity is later supplied.
- Different user on the same browser: Verly will not merge contacts across different external IDs.
- Missing fields: Omitted fields are preserved on the existing contact.
- Metadata updates:
custom_attributesare shallow-merged into contact metadata.
Failure Behavior
Identity verification is designed to fail safely:
- Invalid or malformed JWT: the chat continues in anonymous mode.
- Expired JWT: the session degrades to anonymous mode until you identify the user again with a fresh token.
- Oversized metadata payloads: identity metadata beyond the supported size limits is ignored.
End users do not see an error banner when this happens. The conversation still works, but user-aware actions and personalization will not.
Troubleshooting
The chatbot does not recognize the user
Check the following:
- Identity verification is enabled in Deploy > Identity.
- Your backend is signing with the current secret.
- The JWT includes
suboruser_id. - The token is not expired.
- You are calling
window.conversly("identify", ...), not a stale integration helper.
{{contact.*}} variables are empty in actions
Check the following:
- The relevant fields are inside the JWT, not only in public metadata.
custom_attributeskeys are flat and spelled exactly as referenced.- The JWT is being sent before the action-triggering message.
Users share a browser and see the wrong session
Always call window.conversly("resetUser") during logout or account switching.
Best Practices
- Keep JWT lifetimes short. One hour is a good default.
- Put action-critical data inside the JWT
custom_attributes, not only in public metadata. - Re-issue a fresh token after login, account switching, or secret rotation.
- Treat identity verification as best-effort and make your frontend resilient to anonymous fallback.