Skip to main content
This guide takes you from an empty React app to a working Digital Worker chat embedded in your product.

Prerequisites

  • A React 18 or 19 application.
  • A publishable key for your workspace (pk_live_… or pk_test_…) and a JWT secret — generate both in your workspace’s API keys section (see Publishable Keys & Secrets).
  • A backend that can sign a short-lived JWT with that secret. The secret must never reach the browser.
This guide covers embedding the SDK in your own application, where your backend mints the token. If you’re building a Statisfy-hosted Portal instead, the token comes from POST /sdk/auth rather than your backend — see step 2.

1. Install

Import the stylesheet once, near your app’s entry point:

2. Supply a session token

Every SDK request carries a short-lived HS256 bearer token. The SDK never mints it — it calls your getToken callback and sends whatever you return. How you produce it depends on where the SDK is running.

In your own app: your backend signs it

Sign a short-lived JWT with your JWT secret on your server, and expose an endpoint your frontend can call. Statisfy requires the standard iat / exp claims plus the end user’s email, which is how the gateway resolves who is asking.
Never sign tokens in the browser. Only the publishable key is safe to ship client-side — the JWT secret is a server credential.

In a Statisfy Portal: exchange at /sdk/auth

A hosted portal has no backend of its own, so it exchanges the signed-in user’s identity (your tenant’s Clerk/IdP JWT) for a Statisfy session token:
The user’s email must belong to a customer (a person on an account) of your tenant — that’s how the gateway binds the session to a customer_id. If the email isn’t a customer, /sdk/auth returns 403 not_a_customer.
Either way the token is short-lived (the exchange defaults to 1 hour, see expires_in). Cache it and re-mint before it expires — getToken (next step) is the natural place to do that.
Only the portal exchange produces a customer_id claim, and the project endpoints require one. With a token your own backend signed, the Digital Worker chat works but ProjectModule / useProject return 401 not_a_portal_session.

3. Wrap your app in StatisfyProvider

StatisfyProvider supplies the gateway URL and authentication to every SDK component via React context. Set it up once, high in your tree:
Pass a stable getToken (wrap it in useCallback). The provider memoizes its context value on getToken identity, so an inline arrow function would re-render every consumer on each render.

4. Render your first component

Now any SDK component works anywhere beneath the provider — no connection props needed.
That’s a fully working chat: streaming replies, conversation history, unread tracking, and inline forms. See Digital Worker Chat for every prop and the headless client. To show a customer’s onboarding project instead, render the Project Module:

5. Theming (optional)

The SDK’s styles are self-contained and prefixed (dw:) so they won’t collide with your app’s CSS. Override the look with --dw-* CSS variables on any ancestor of the SDK components:

Troubleshooting

The bearer token’s signature didn’t verify. Make sure getToken returns the session token from /sdk/auth (not the raw Clerk JWT), that it hasn’t expired, and that you minted it against the same tenant/environment your gateway points at.
The signed-in user’s email isn’t a person on any account in your tenant. Add them as a contact, or sign in as a known customer.
An SDK component is mounted outside the provider. Move it beneath <StatisfyProvider>.