Session tokens (REST API)
The REST API your backend calls to create, inspect and invalidate chat sessions.
1. Your backend -> POST /api/v1/chat/sessions (identity + lifetime)
2. Your backend <- signed JWT + session record
3. Your backend -> return the token with your own session
4. Your frontend -> hand the token to the chat SDK
5. Chat SDK -> the customer chats, already identified
6. On sign-out -> DELETE /api/v1/chat/sessions?customerId=...Endpointslink
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/chat/sessions | Create a session and get its token |
GET | /api/v1/chat/sessions?customerId= | The customer's live session, if any |
GET | /api/v1/chat/sessions/{sessionId} | Has this session been invalidated? |
DELETE | /api/v1/chat/sessions/{sessionId}?customerId= | Invalidate that session |
DELETE | /api/v1/chat/sessions?customerId= | Invalidate whatever session the customer holds |
Every endpoint requires a secret-bearing API key (x-api-key: keyId:secret) and is server-to-server only. A publishable widget key is refused.
Lifetime is yours to chooselink
expiresInSeconds accepts 60 to 86400 (24 hours), defaulting to 3600. Want six-hour sessions? Send 21600. Values above the maximum are refused rather than silently clamped, so you always know what you got.
One live session per customerlink
A customer holds at most one session. POST returns 409 with the offending session while one is still valid; mint again once it expires or after you revoke it. This is deliberate: it keeps one identity from accumulating parallel credentials, and it means "the customer's session" is always a single, knowable thing.
How little is storedlink
The token is a JWT, and it is the session: the customer id, name, email, phone and metadata are all signed into it. Verifying the signature yields the whole identity, so the platform keeps no copy of any of it. Duplicating it server side would only create state that could drift from the claims.
Redis holds two things, and nothing else:
| Key | Written when | Lives for |
|---|---|---|
chat:revoked:{org}:{jti} | only when you invalidate a session early | the token's remaining lifetime |
chat:live:{org}:{customerId} | on every mint, to enforce the one-session rule | the token's lifetime |
A session that lives out its natural life is never written to the denylist at all. Both keys carry the token's own TTL, so the store empties itself and nothing has to be swept.
A denylist rather than an allowlist is deliberate: if Redis is briefly unreachable, an allowlist would reject every token and take chat down, while a denylist degrades to "an early revocation may be missed until Redis returns", bounded by the lifetime you chose. Short lifetimes make that window small, which is another reason to prefer them.
The token itself is never stored either. If you lose it, revoke the session and mint another.
/api/v1/chat/sessionsGet a customer's live session
Does this customer already hold a live session?
Call it before minting to decide whether to reuse the token you have, wait, or revoke and re-mint. A customer can hold at most one session, so sessions is a list of zero or one.
Expired and revoked sessions are simply absent: the record exists only while the session is usable.
Headers
x-api-keyParameters
customerIdquerystringdefault: Responses
200 – Live session found
{
"sessions": [
{
"sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
"customerId": "usr_123",
"status": "active",
"expiresAt": "2026-08-26T16:00:00.000Z",
"expiresInSeconds": 21600
}
]
}https://api.newinstance.cloud/api/v1/chat/sessionsQuery parameters
customerIdHeaders
x-api-keyCode samples
curl -X GET 'https://api.newinstance.cloud/api/v1/chat/sessions'/api/v1/chat/sessionsCreate a chat session
Mint a chat session for one of your customers, from YOUR backend.
Call this wherever your app establishes a session - typically right after sign-in - and return the token to your frontend alongside your own session token. The frontend hands that one string to the chat SDK and passes nothing else.
Bodylink
| Field | Type | Required | Notes |
|---|---|---|---|
customerId | string | Yes | Your stable id for this customer. Becomes the verified customer id. Max 200 characters. |
customerName | string | Yes | Name on the conversation and on every message they send. Max 200. |
customerEmail | string | No | Also satisfies the merchant Require customer email setting, so the visitor is never asked. Max 320. |
customerPhone | string | No | Shown to agents. Max 40. |
metadata | object | No | Extra agent-visible context. At most 20 keys, 64-character keys, 500-character values, 4096 bytes total. Nested objects and arrays are rejected rather than flattened. |
expiresInSeconds | integer | No | You choose the lifetime: 60 to 86400 seconds (24 hours max). Defaults to 3600. The example asks for 6 hours. |
One live session per customerlink
A customer may hold one session at a time. While theirs is still valid this returns 409 with the session that is in the way. Once it expires, or you revoke it, a new one can be minted. That keeps a single identity from accumulating parallel credentials.
The 409 body carries the existing session, so you can decide whether to keep using the token you already have or revoke and re-mint.
What comes backlink
A signed JWT, and a session summary that is only an id, its owner and its expiry.
Everything about the customer is inside the token, signed. The platform keeps no copy: it would be duplicated state that could drift from the claims, and the JWT already answers the question. If you need the name, email, phone or metadata back, decode the token.
The token is never stored either. If you lose it, revoke the session and mint another.
Headers
x-api-keyRequest body
application/json{
"customerId": "usr_123",
"customerName": "Ada Lovelace",
"customerEmail": "ada@example.com",
"customerPhone": "+44 20 7946 0958",
"metadata": {
"plan": "enterprise",
"accountNumber": "AC-4471"
},
"expiresInSeconds": 21600
}Responses
201 – Session created
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiM2YxYzJkNGU1YTY3ODkwIiwic3ViIjoidXNyXzEyMyJ9.7mK2_x1s0KQ",
"expiresAt": "2026-08-26T16:00:00.000Z",
"expiresInSeconds": 21600,
"session": {
"sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
"customerId": "usr_123",
"status": "active",
"expiresAt": "2026-08-26T16:00:00.000Z",
"expiresInSeconds": 21600
}
}https://api.newinstance.cloud/api/v1/chat/sessionsHeaders
x-api-keyRequest body
Code samples
curl -X POST 'https://api.newinstance.cloud/api/v1/chat/sessions' \
-H 'Content-Type: application/json' \
--data-raw '{
"customerId": "usr_123",
"customerName": "Ada Lovelace",
"customerEmail": "ada@example.com",
"customerPhone": "+44 20 7946 0958",
"metadata": {
"plan": "enterprise",
"accountNumber": "AC-4471"
},
"expiresInSeconds": 21600
}'/api/v1/chat/sessionsInvalidate a session
Kill a session before it expires.
The token stops being accepted on the next request: its signature is still valid and it has not expired, but the session is on the denylist. There is no propagation delay and no cache to wait out.
customerId is required so the customer's slot is freed as well as the token denied. Without it the token would die but the customer would stay blocked from a new session until natural expiry. You always have it: it is what you minted the session for.
Idempotent, so a retried sign-out is safe.
Headers
x-api-keyParameters
customerIdquerystringdefault: Responses
200 – Session invalidated
{
"sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
"session": {
"sessionId": "b3f1c2d4e5a67890b1c2d3e4f5a6b7c8",
"customerId": "usr_123",
"status": "active",
"expiresAt": "2026-08-26T16:00:00.000Z",
"expiresInSeconds": 21600
},
"revoked": true
}https://api.newinstance.cloud/api/v1/chat/sessionsQuery parameters
customerIdHeaders
x-api-keyCode samples
curl -X DELETE 'https://api.newinstance.cloud/api/v1/chat/sessions'