Our outage means “no new sessions”. It never means “everybody is logged out”.
Why the difference exists
Because verification does not reach us. A contact session buys a short-lived JWT, and your backend verifies it against two published documents rather than by calling us:
All three are public, addressed by the one identifier your config already holds, and
cacheable by anything between you and us. Which means: while we are down, a request
to your backend carrying a valid token is answered by your backend, out of a
document it already has. Nothing about that request touches us.
The headers, and the second one that matters
stale-while-revalidate covers a refetch that is slow. stale-if-error covers
one that comes back an error, times out, or cannot be made at all — and it is the
directive that actually describes our outage. A cache in front of these documents
honours both; most JWKS clients honour them too. If you put our documents behind
your own CDN or reverse proxy, do not strip them.
What stops and what continues
Read this as two columns rather than as a list of outages. The left column is what you should expect to see fail; the right is what must keep working, and what to open a ticket about if it does not.
Two things are worth stating because they are the ones people get wrong:
A token refresh is not a new session.
POST /v1/contact/token is a read on our
side — it does not create anything. It is the call your SDK makes every few minutes
for as long as somebody is signed in, and it is built to keep answering in
conditions where a sign-in would not. Treat a failure there as transient.
Exceeding a limit never blocks a sign-in. Neither the active-contact meter nor
the send allowance is read by any authentication path. A limit produces a warning
and an invoice, never a 402 in front of your users.
The four lines on your side
Our half is above. This is yours, and it is short.1
Cache the key set
Fetch
GET /v1/jwks/{publishableKey} once and cache it. The libraries in
Session tokens do this for you.
A verifier that fetches per request has moved our uptime back in front of every
request to your API — the one thing the JWT exists to avoid.2
Poll the revocation list, and keep verifying when you cannot fetch it
A list you could not refresh degrades to “I cannot revoke faster than the token
expires” — which is the bound that always existed. It must never degrade to
refusing everything. A revocation list that locks your customers out whenever it
is unreachable is a worse failure than the one it prevents.
3
Retry a failed refresh; never sign somebody out over one
getToken() failing is a network event, not a logout. Clear a session when the
API says the session is gone — a 401 on the contact surface — and not because
a call did not come back.4
Render your sign-in screen from a cached config
GET /v1/config/{publishableKey} tells a screen what to draw. Cache it (the
header already asks you to) so the form renders even on a minute when we cannot
answer. A screen that cannot paint because a configuration read failed is our
outage becoming yours for no reason.@userkit/nextjs’s verifyContactToken already implements all four, and
claims.revocationCheck tells you which of fresh, stale or unavailable you
are in. Pass requireRevocationCheck: true for the handful of actions where
unavailable should refuse — and leave it off everywhere else, which is the whole
point.What we do not degrade
One thing runs the other way, and it is worth knowing before it surprises you: a captcha that is configured and cannot be verified refuses the sign-up with503 captcha_unavailable. It does not pass.
That is not an oversight in the degradation story, it is the one place the story
inverts. A control that opens whenever its provider is slow is missing on precisely
the day the traffic making it slow is the reason it was turned on. If you would
rather a provider outage let sign-ups through, the decision is yours to make by
turning the requirement off — not one we make for you during an incident.