Backend token and ticket mint

Your backend authenticates your own users and issues short-lived chat access. Your tenant server key and the mint token stay on the backend.

Mint for a verified user and approved origin

Derive the stable end_user from your verified application session. Choose origin from server configuration; do not trust a user ID or arbitrary origin in a browser request. Embed mint accepts tenant vk_live_ or vk_ent_ keys. It does not need a privileged native session-mint key.

HTTP
POST https://chat.example.com/api/v1/embed/tokens
Authorization: Bearer <tenant server key>
Content-Type: application/json

{"end_user":"user_123","origin":"https://app.example","ttl_s":900}

HTTP 201 returns token, tenant_id and expires_in_seconds. TTL is at most 900 seconds. Profile fields and claims are not part of this mint contract. A successful mint is not a promise that a paid turn will pass balance or spending limits.

Issue a single-use frame ticket

Keep the vt_ token on the backend. Generate a fresh random nonce of 16–128 URL-safe characters for each ticket and use the same exact approved Origin on the ticket request.

HTTP
POST https://chat.example.com/api/v1/embed/frame/tickets
Authorization: Bearer <the vt_ mint token>
Origin: https://app.example
Content-Type: application/json

{"nonce":"a-fresh-random-url-safe-nonce"}

The response includes a vf_ ticket and its lifetime. Do not retry an already redeemed ticket. Your backend supplies the active signed web configuration from the harness through the authorized configuration flow in your delivered client quickstart. Never synthesize a signature or replace a refused configuration with an unsigned document.

Return the ticket envelope

Text
{
  "ticket": "vf_...",
  "nonce": "the-same-fresh-nonce",
  "signedConfig": { "...": "the-active-signed-web-document" }
}

Return this envelope only to the authenticated application user, with Cache-Control: no-store. The browser passes it through the loader's getSession callback. Its independently pinned publicKeys verify signedConfig. The loader redeems the ticket using the real host Origin and holds the resulting scoped session in memory.

Renew and isolate users

Issue a fresh ticket on renewal. Preserve the same tenant and user during a mounted session. Unmount on logout or account change and refuse your backend endpoint after logout. The browser never receives a server key, a console session, or a privileged native session.

Continue to the first embedded chat.

Backend example

Authenticate the caller, apply CSRF protection and derive endUserId from the verified application session before invoking this function. Return the result with Cache-Control: no-store only to that user. The signed configuration route is authorized by the backend token and approved Origin.

JavaScript
import { randomBytes } from 'node:crypto';
const chatBase = 'https://chat.example.com'; // custom tenant host if configured
const siteOrigin = 'https://app.example.com';
const tenantKey = process.env.TENANT_CHAT_KEY;

async function checked(path, options) {
  const response = await fetch(chatBase + path, { ...options, redirect: 'error' });
  if (!response.ok) throw new Error('Chat could not open.');
  return response.json();
}

export async function chatSession(endUserId) {
  const minted = await checked('/api/v1/embed/tokens', {
    method: 'POST',
    headers: { authorization: `Bearer ${tenantKey}`, 'content-type': 'application/json' },
    body: JSON.stringify({ end_user: endUserId, origin: siteOrigin, ttl_s: 900 }),
  });
  const nonce = randomBytes(16).toString('hex');
  const config = await checked(`/v1/config?org=${encodeURIComponent(minted.tenant_id)}&surface=web`, {
    headers: { authorization: `Bearer ${minted.token}`, origin: siteOrigin },
  });
  const ticket = await checked('/api/v1/embed/frame/tickets', {
    method: 'POST',
    headers: { authorization: `Bearer ${minted.token}`, origin: siteOrigin, 'content-type': 'application/json' },
    body: JSON.stringify({ nonce }),
  });
  return { ticket: ticket.ticket, nonce, signedConfig: config };
}