This is the auth model for every Courier client SDK. One sign-in covers Inbox, Toasts, the preference center, and mobile push tokens.
How the JWT flow works
The private API key that signs a JWT stays on your server, so your backend mints every token, never client code:1
Your app calls your backend
When a user signs in, your app requests a token from your backend (for example,
GET /api/courier-jwt).2
Your backend calls Courier
Your backend uses your to call the with the scopes the user needs. Keys are per . Use the one matching the environment you’re issuing tokens for.
3
Your backend returns the JWT to the client
The client passes the token to the SDK through
signIn({ userId, jwt }).1. Issue a token
Call from your backend with your API key and a scope string:Scopes
scope is one space-separated string. Copy the line for what you are building, and swap in your user’s id.
The
Use whatever your app already calls this user, such as a database id or your auth provider’s
user_id is yours, not Courier’s.Use whatever your app already calls this user, such as a database id or your auth provider’s
sub. Nothing has to exist in Courier first, since signing in registers the user. The same id goes in two places, user_id: in the scope and the userId you pass to signIn, and they have to match.Inbox and Toasts
Add the preference center
Add mobile push
Everything
user_id: and the two inbox scopes. What each one grants:
For every scope value, see the .
2. Sign in on the client
Signing in tells the SDK which user it is fetching messages for. Until it runs, the components render empty. Pass the JWT tosignIn when your app starts or the user logs in:
3. Read the signed-in user
Every platform exposes the current user and a listener that fires on sign-in and sign-out. Use the listener to clear app state on sign-out, or to re-render once credentials are restored on launch.On mobile, credentials persist across app sessions. The SDK restores the last signed-in user on launch, so no cold start needs
signIn. Call it again only when the user changes or the token expires.4. Sign out
CallsignOut when the user logs out of your app. It clears stored credentials, disconnects the real-time socket, and on mobile stops associating push tokens with that user.
5. Refresh tokens
Setexpires_in to match your session length. A short-lived token can expire while a tab stays open, which silently empties the inbox.
The SDKs do not refresh tokens. Before the current token expires, mint a new JWT on your backend and call signIn again with it.
EU-hosted workspaces
Skip this section unless your workspace is on the . Every client SDK defaults to Courier’s US hosts. Pass regional URLs throughapiUrls on signIn:
@trycourier/courier-js:
For the
CourierApiUrls type shape, see .
Troubleshooting
The inbox loads but shows no messages. The SDK connects and throws nothing, so check these four causes in order. Decode the JWT at jwt.io for the first three:expis in the future. A token that expired between page loads signs in without error and returns nothing.scopeincludesinbox:read:messagesandinbox:write:events. Without them the connection succeeds and the feed is empty.- The
user_id:in the scope matches theuserIdyou pass tosignIn. A mismatch reads an empty inbox belonging to whoever the token names. - The
tenantIdmatches the send. AtenantIdfilters the feed, and the unread count, to that tenant. A message sent without a tenant, or in a different one, will not appear. Signing in without atenantIdapplies no filter. See .
read:preferences write:preferences to the scope string.
Push tokens never appear on the user. Add write:user-tokens to the scope string. covers the registration that follows.
Everything 401s on an EU workspace. The token and the client must be in the same region. Confirm your backend mints against https://api.eu.courier.com/auth/issue-token and the client passes EU apiUrls.
FAQ
Why does the inbox connect but show no messages?
Why does the inbox connect but show no messages?
The token signed in but lacks read access. Confirm the scope string includes
inbox:read:messages and inbox:write:events, and that exp has not passed.Do Inbox and Toasts need separate tokens?
Do Inbox and Toasts need separate tokens?
They read the same feed, so one
signIn with the same JWT and scopes covers both.I'm on an older inbox SDK with a different auth setup.
I'm on an older inbox SDK with a different auth setup.
Older SDK versions used a different authentication method. To move to JWT, see the .