Integrate

Player identity

A player id is never accepted directly from the browser. Your server signs a short-lived token, your page hands it to the widget, and the platform verifies the signature.

#Why it matters

Once a visitor is linked to a verified player:

  • agents see the verified player id and operator data in the conversation;
  • the AI agent can read account data such as balance, deposits and bonus history from your operator API (for that player only);
  • actions such as bonuses can only be requested for verified players.

In calls to your operator API the player id is always filled in from this token; it cannot be changed by anything the visitor types.

#Flow

  1. Get the secret

    In the admin console, on Websites & widget, click Reveal identity secret (audited) (requires websites:write; each reveal is written to the audit log). Each website has its own secret, shown base64url-encoded. Keep it on your server only.

  2. Sign a token on your server

    When the player logs in, base64url-decode the secret and use the resulting bytes to sign an HS256 JWT (see the examples below).

  3. Hand the token to the widget

    Your page calls window.TwoPeople.identify(token). The widget sends the token to POST/api/v1/widget/identity; the platform verifies the signature and claims and links the visitor to the player.

#Token format

JWT
// header
{ "alg": "HS256", "typ": "JWT" }
// payload
{
  "sub": "player-48213",          // your player id
  "aud": "wk_…",                  // the website's widget key
  "iat": 1767225600,              // issued at (unix seconds)
  "exp": 1767225900,              // expiry, at most iat + 300
  "jti": "5f0c2a8e-…"             // unique token id (single use)
}
NameTypeDescription
algrequiredHS256HS256 is the only accepted algorithm.
subrequiredstringThe player id in your operator system. 1–128 characters: letters, digits and _ . : @ - only.
audrequiredstringThe website’s widget key (wk_…).
iatrequirednumberIssued-at time (Unix seconds). Tokens older than 5 minutes from iat are rejected.
exprequirednumberExpiry. Must be iat + 300 or earlier.
jtirequiredstringUnique token id (up to 128 characters). Every token can be used only once.

#Server examples

The examples read the widget key and the secret from environment variables: TWOPEOPLE_WIDGET_KEY and TWOPEOPLE_IDENTITY_SECRET.

// npm install jose
import { SignJWT } from 'jose';
import { randomUUID } from 'node:crypto';

// The secret is shown base64url-encoded: decode it to bytes before signing.
const secret = Buffer.from(process.env.TWOPEOPLE_IDENTITY_SECRET, 'base64url');
const widgetKey = process.env.TWOPEOPLE_WIDGET_KEY; // wk_…

export async function playerIdentityToken(playerId) {
  const now = Math.floor(Date.now() / 1000);
  return new SignJWT({})
    .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
    .setSubject(String(playerId)) // your player id
    .setAudience(widgetKey)
    .setJti(randomUUID()) // unique per token
    .setIssuedAt(now)
    .setExpirationTime(now + 300) // at most 5 minutes
    .sign(secret);
}

#On the page

Because the token is short-lived and single-use, the safest approach is to fetch it from your own server when you need it. window.TwoPeople is defined once the widget has loaded:

JavaScript
// Runs on your page after the player has logged in.
// /api/support/identity-token is an endpoint on YOUR backend that returns
// { token } for the logged-in player (see the server examples above).
function whenWidgetReady(callback) {
  if (window.TwoPeople) return callback(window.TwoPeople);
  setTimeout(() => whenWidgetReady(callback), 200);
}

whenWidgetReady(async (widget) => {
  const res = await fetch('/api/support/identity-token', {
    method: 'POST',
    credentials: 'include',
  });
  if (!res.ok) return;
  const { token } = await res.json();
  const linked = await widget.identify(token); // true when the player is verified
  if (!linked) console.warn('2people: player identity was not accepted');
});

identify() resolves to true when the player is linked and to false when the token is rejected or the request fails. An open conversation is refreshed after linking.

#Errors

When POST/api/v1/widget/identity rejects a token it returns application/problem+json:

HTTPcodeReason
401INVALID_IDENTITY_TOKENSignature, algorithm, aud, time (exp / 5-minute age limit) or claims are invalid.
401IDENTITY_TOKEN_REPLAYEDA token with the same jti was already used.