Server-side token generation (SSO)¶
The Trip Planner Widget, SSO Button Widget, and Headless SDK request a signed token from your backend. Traveln verifies it on /sso-login/ and creates a tenant session.
Token endpoint¶
Implement a POST endpoint (for example /api/traveln/sso/) that returns:
{ "token": "..." }
Request payloads¶
The two web embeds send slightly different client payloads to your token endpoint.
Trip Planner Widget request¶
{ "prompt": "string", "trace_id": "string" }
SSO Button Widget request¶
{ "trace_id": "string" }
Headless SDK request¶
{ "prompt": "string", "trace_id": "string" }
The SDK sends prompt only when the launch action needs it. Accommodation filters and redirect targets are forwarded during the /sso-login/ redirect, not to your token endpoint.
You can ignore fields you do not need. The button target itself is controlled by data-button-type on the Traveln loader and is forwarded by Traveln during the /sso-login/ redirect.
Token format¶
This is not a JWT.
The token is:
base64url(payload_json_bytes) + "." + base64url(hmac_sha256(secret, payload_json_bytes))
Required payload fields¶
tenant_slug(string)user_id(string)ts(integer, unix seconds)nonce(string, unique per token)
Optional payload fields¶
first_name,last_name,email,phone,picturehost(string): when present, Traveln binds the token to both the request host and the token host (both must be registered for the tenant).is_anonymous(bool/string): when true or identity is missing, Traveln may create a "Guest User" session.
Identity modes¶
| Mode | Payload guidance | Traveln behavior |
|---|---|---|
| Known user | Send stable user_id plus available first_name, last_name, email, phone, and picture. | Creates a non-anonymous SILENT session. Tenant UI can show account controls. |
| Guest or anonymous | Send is_anonymous: true or omit required identity fields. | Creates a guest session and marks it as anonymous for tenant UI. |
For known users, prefer a stable partner user identifier in user_id. For anonymous users, still send a unique user_id and one-time nonce; do not reuse SSO tokens.
Replay protection
Nonces are one-time use and timestamps are short-lived. Do not reuse tokens.
Example (Python)¶
import base64
import hmac
import json
import time
from hashlib import sha256
from uuid import uuid4
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
def create_traveln_sso_token(*, tenant_slug: str, user_id: str, secret: str, host: str | None = None) -> str:
payload = {
"tenant_slug": tenant_slug,
"user_id": user_id,
"ts": int(time.time()),
"nonce": uuid4().hex,
}
if host:
payload["host"] = host
payload_bytes = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
sig = hmac.new(secret.encode("utf-8"), payload_bytes, sha256).digest()
return f"{b64url(payload_bytes)}.{b64url(sig)}"
Example with user identity¶
payload = {
"tenant_slug": "your-tenant-slug",
"host": "partner.example.com",
"user_id": "partner-user-123",
"first_name": "Amina",
"last_name": "Hassan",
"email": "amina@example.com",
"phone": "+201000000000",
"ts": int(time.time()),
"nonce": uuid4().hex,
}
Example anonymous payload¶
payload = {
"tenant_slug": "your-tenant-slug",
"host": "partner.example.com",
"user_id": "guest-session-123",
"is_anonymous": True,
"ts": int(time.time()),
"nonce": uuid4().hex,
}
Runtime behavior after token creation¶
Once your endpoint returns { "token": "..." }:
- Traveln receives the signed token.
- Traveln verifies the tenant, timestamp, nonce, and HMAC signature.
- Traveln creates a local session for the user.
- Traveln redirects the user to the destination requested by the widget flow:
- Trip planner widget ->
/ai-trip-planner/ - Button widget with
trip_planner->/ai-trip-planner/ - Button widget with
trips->/trips/ - Button widget with
accommodation_search->/accommodation-search/ - Headless SDK targets -> the matching planner, trips, or accommodation destination