Every token that signs a reader in to your help center follows one contract, whether it comes back from your Login URL to /sso/jwt, arrives in an iframe's ?jwt=, or rides in the widget's Authorization header. This page lists every claim and every check HelpCenter.io applies.
A token HelpCenter.io accepts, decoded. The header:
{
"alg": "HS256",
"typ": "JWT"
}
The payload, for sign-in through your Login URL:
{
"jti": "kT9wQ48213pZ2mV",
"iat": 1767225600,
"exp": 1767225900,
"email": "jane.doe@example.com",
"name": "Jane Doe",
"external_id": "4815"
}
Signing
Algorithm: HS256 (HMAC with SHA-256) only. A token signed with HS384, HS512 or RS256, or with
algset tonone, is rejected even when its signature is correct.Key: the Shared secret from your settings, exactly as shown, used as a string. It has at least 64 characters, and a generated one looks like base64, but it isn't decoded: a token signed with the decoded bytes is rejected.
Header:
algis required.typis optional, andkidis ignored.
Claims
Claim | Type | Rule | What HelpCenter.io does with it |
|---|---|---|---|
| string | Required. For sign-in through your Login URL, the | Makes each token single use (see below). |
| number, Unix seconds | Required. At most 30 seconds in the future. For the widget, also at most Token TTL plus 30 seconds in the past. | Checked only. |
| number, Unix seconds | Required. The token works until 30 seconds after | Checked only. |
| number, Unix seconds | Optional. At most 30 seconds in the future. | Checked only. |
| string | Required, not empty. Its format isn't checked. Up to 240 characters. | Identifies the reader, together with |
| string | Required, not empty. | Split at the first space into a first and last name, and shown on the reader's comments. Saved when the reader is created. |
| string | Optional, recommended: your own stable user ID. A number also works. Up to 140 characters. | Identifies the reader, together with |
| string | Required when your settings have an Issuer, and then equal to it, letter case included. Ignored otherwise. | Checked only. |
| string | Required when your settings have an Audience, and then equal to it. Always one string, never a list. | Checked only. |
| string | Optional. Up to 10 characters. | Saved when the reader is created (otherwise your help center's default language is saved). The help center doesn't use it. |
| string | Optional. Up to 255 characters. | Saved when the reader is created. The help center doesn't show it. |
| object | Optional. | Saved when the reader is created. The help center doesn't use it. |
| any | Ignored. | Every SSO visitor is a reader, whatever the claim says. |
| any | Ignored. | Nothing. |
Numbers sent as strings, such as "iat": "1767225600", are accepted, and so are fractions of a second.
Time checks
All times are Unix timestamps in seconds. HelpCenter.io allows 30 seconds of difference between your clock and its own:
Check | Accepted | Rejected |
|---|---|---|
| Up to 30 seconds | More than 30 seconds |
| Up to 30 seconds | More than 30 seconds |
| Less than 30 seconds | 30 seconds or more |
Widget only: seconds since | Up to Token TTL plus 30 | More than Token TTL plus 30 |
For example, a token whose exp passed 20 seconds ago still signs a reader in, and one whose exp passed 40 seconds ago doesn't. With Token TTL at 300, the widget accepts a token issued 320 seconds ago and rejects one issued 340 seconds ago.
Milliseconds are the most common mistake: Date.now() in JavaScript returns milliseconds, which puts iat thousands of years in the future, and the token is rejected. Use Math.floor(Date.now() / 1000).
Single use
Sign-in through your Login URL:
jtimust be thekeyHelpCenter.io sent to your Login URL. Each key works for one successful sign-in, on the help center that issued it. A token HelpCenter.io rejects doesn't use up the key.Iframe and widget:
jtican be any value you haven't used before on this help center, such as a UUID. Use eachjtionce. When HelpCenter.io sees ajtiit already accepted, an iframe page with a reused token loads as if it had none, and on a private help center the widget answers403 Forbidden. The iframe and the widget share this record, so a token used in an iframe can't be reused in the widget.
How long tokens and sessions last
Flow | The token is accepted while | After sign-in |
|---|---|---|
Login URL ( |
| A session that lasts until 24 hours pass without a visit |
Iframe ( |
| The same session, kept only when your page and the help center are on the same site |
Widget ( |
| Nothing: each request carries its own token |
Token TTL is set in your settings, from 30 to 86,400 seconds, and 300 is recommended. It applies to widget tokens only. For how long a token should live, see How long should my SSO tokens live?
What the reader record keeps
The first valid token for a person creates their reader record. After that:
Details are written once.
name,email,external_id,lang,avatar_urlandcustom_fieldskeep the values from the first sign-in. For example, a reader who first signed in as "Mary Jane Watson" (first name Mary, last name Jane Watson) keeps that name when a later token says "Mary Watson-Parker", and a newavatar_url,langorcustom_fieldsin that token is ignored.A known
external_idwins over a new email. A token with a newemailand a knownexternal_idsigns in the existing reader, and the saved email stays the first one.external_idis saved only at creation. A reader created without one stays matched by email only. If their email changes later, the next token creates a second reader, even with anexternal_id. Sendexternal_idfrom the first sign-in. See What if a reader's email changes on our side?Letter case doesn't matter.
Jane.Doe@Example.comandjane.doe@example.comsign in the same reader.
Tokens that cause a server error
A few tokens fail with a server error (HTTP 500) instead of being rejected, in all three flows. Readers see HelpCenter.io's "Whoops, something crashed." page, and widget requests fail:
audas a list, such as["helpcenter-prod", "other"], even when no Audience is set. Some libraries produce a list when you give them several audiences.email,nameor another string claim sent as an object or a list.On a reader's first sign-in:
external_id,langoravatar_urlover the limits in the table above.
An email over its limit doesn't cause an error: through your Login URL the reader lands on the HelpCenter.io sign-in page, and in an iframe or the widget the token is refused like an invalid one. The token checker in Troubleshoot single sign-on catches all of these.
What HelpCenter.io answers
Outcome | Login URL ( | Iframe ( | Widget |
|---|---|---|---|
Token accepted |
| The page, signed in | The data, as that reader |
Token rejected (signature, algorithm, missing claim, time, Issuer or Audience) |
| The token is ignored and the visitor stays signed out |
|
|
| Not checked | Not checked |
No token |
| A private help center sends the visitor to your Login URL |
|
HelpCenter.io doesn't say why it rejected a token. Troubleshoot single sign-on has a checker that applies the same rules and names the one a token breaks.