Protected agents
A protected agent only talks to signed-in chatters. Your backend mints a short-lived JWT signed with the agent's signing secret. Chatvia trusts the signature.
If your team hasn't read Identity verification yet, start there. This page is the embed-specific recipe.
Marking an agent as protected
Open the agent's Security → Identity page. Under Access control, turn Make agent public off. New agents start protected. The flag belongs to the agent, so it applies to all three surfaces at once: Hosted Chat, Chat Widget and Command Bar.
The same page shows the agent's Signing secret. Copy it to your backend. Each agent has its own secret.
A protected agent without a token shows:
- Hosted Chat: the sign-in page.
- Chat Widget: a locked chat window with the notice "This chat requires sign-in." The composer is turned off.
- Command Bar: the same locked screen.
Mint the token on your backend
import jwt from 'jsonwebtoken'
const SIGNING_SECRET = process.env.CHATVIA_SIGNING_SECRET
export function signChatviaToken(chatterId, agentId, ttlSeconds = 3600) {
return jwt.sign(
{ sub: chatterId, aud: agentId },
SIGNING_SECRET,
{ algorithm: 'HS256', expiresIn: ttlSeconds },
)
}sub: the Chatvia ID of the chatter or teammate (a UUID), not an ID from your own system. The account must have access to this agent.aud: the agent ID (same asdata-agent-id).exp: Unix expiry. Max 24 hours from now.
No JWT library? Chatvia also accepts a short HMAC token: cvh1.<sub>.<exp>.<sig>, where <sig> is the lowercase hex HMAC-SHA256 of <sub>.<exp>, made with the signing secret.
Hand the token to the embed
Widget: data-auth
Render the token into the script tag from your team's server-side template:
<script id="chatbot-embed-script"
src="https://chat.example.com/shakomako/embed.js"
data-agent-id="YOUR_AGENT_ID"
data-auth="<%= chatviaToken %>"
defer></script>https://chat.example.com stands for your install's own address (APP_URL). For a protected agent, the snippets on the Deploy page already carry a data-auth="YOUR_SIGNED_TOKEN" placeholder.
On load, the widget swaps the token once for its own session token. To sign a chatter in again, mint a new token and render the page again.
Hosted Chat: ?token=
Redirect signed-in chatters from your app:
res.redirect(`https://chat.example.com/chat/YOUR_AGENT_ID?token=${chatviaToken}`)Chatvia validates the token, signs the chatter in to the chat, and strips ?token= from the URL. From then on the chatter is an ordinary signed-in chat user.
Refresh strategy
Tokens live up to 24 hours. Don't try to mint longer ones. Chatvia rejects them.
Options for keeping a long chatter session alive:
- Re-mint per page load. Cheapest, fits server-rendered apps.
- Mint a fresh token when the chatter comes back. SPA apps: when your own session renews, mint a new JWT on your backend and load the widget with it on the next page load.
- Let it expire. If your team's sessions are short anyway (online banking, sensitive tools), letting the chat session end with the JWT is fine.
What rejection looks like
A bad token (expired, wrong audience, wrong signature) resolves to "not signed in." The agent then behaves as if the request were unauthenticated, per surface:
- Hosted Chat: the sign-in page.
- Chat Widget: the locked window with "This chat requires sign-in."
- Command Bar: the same locked screen.
Chatvia never returns a partial response with a half-authenticated identity. It's signed in or not, with nothing in between.
Auditing access
A signed-in chat belongs to the chatter's Chatvia account, so each chatter only sees their own conversations. The agent's Chatters page lists the chatters who have access to the agent. A token only works while its chatter still has access: once the chatter loses access or is deactivated, their tokens stop working at once.
Common mistakes
- Wrong audience.
audmust be the agent's external ID, not its name or chat address. - Wrong subject.
submust be the Chatvia ID of the workspace owner, a teammate, or a chatter who has been granted access to this agent. Just any string isn't enough. - Token in URL hash. Don't put the token in
#fragment. It ends up in browser history and can leak. Use?token=(consumed and stripped) ordata-auth(never in the URL). - Signing on the client. Never ship the signing secret to the browser. If it's bundled in JS, anyone can mint sign-ins as anyone.