- Read the customer from your own login, never from the request body.
- Answer 401 only when nobody is signed in. The loader treats your 401 as “your user signed out”.
- Create the session with doola using your
dk_secret key. - Return only
accessTokenandexpiresIn, withCache-Control: no-store.
Next.js and web-standard runtimes
@doola/js/server builds the route for you. createSessionHandler takes a web-standard Request and returns a Response, so it is a Next.js route handler as written, and a one-line wrapper in Remix, Hono, Bun, Deno and Cloudflare Workers (where process.env needs the nodejs_compat flag).
app/doola-session/route.ts
Express, Fastify and your own routing
createCustomerSession returns the status and body to send:
Any other language
Call Create a customer session with your secret key as the wholeAuthorization header, with no Bearer prefix:
Response from doola
- Turn doola’s 401 into a 502. It means your key or account is wrong. Passed through, the loader would read it as “your user signed out” and send a signed-in customer to your login page.
- Pass other errors through with their status, and send a 409 with doola’s
error.codeas{ code }. doola answers 409 for more than one reason, and the loader tells them apart by the code. - Answer 502 when doola cannot be reached or answers with something that is not a session.
- Unwrap the envelope. Send the browser only
accessTokenandexpiresInfrompayload, never the whole body. - Send
Cache-Control: no-storeon every answer, as for any token.
What to send
The founder confirms their legal name, phone and country in the first step of the wizard anyway, so sending them is optional.
The session
- It lasts 10 minutes (
expiresIn: 600). The loader asks your route for a new one at 80% of that, so a mounted SDK renews about every eight minutes, and only while it is mounted. - It acts as that one customer. A
cs_test_orcs_live_token can read and submit only that customer’s formation through the SDK. It cannot call the Partner API. - It needs no storage. Your route mints a fresh session on every call. Never cache one, and never send one in a URL.
Errors from doola
On a renewal, each of these arrives as
renewal_failed, except E_RESOURCE_CONFLICT and E_CUSTOMER_REVOKED, which keep their own types. The current session keeps working until it expires.
For the three 409s, the iframe tells the founder what happened (Customer experience), but only when your route forwards the code. A route that answers a 409 with its status alone gets email_in_use for all three on the first session, and the founder sees “We could not start your session”. For mint_failed, the iframe shows “We could not start your session” with Try again, which calls your route again, up to three times, and then points the founder to your team. E_RESOURCE_CONFLICT usually means your user ids and emails disagree: send the same externalCustomerId for a person every time.
Protect the route
- Authenticate it like any other route that acts as your user, and apply your CSRF protection.
- Accept
POSTonly, so a link or an image tag cannot mint a session. - Never let the browser choose the customer. The email and id come from your session, not the request.
- Send only an email your app has verified for the signed-in user, and always send
externalCustomerId. doola resolves the customer from them, and the code your route forwards on a 409 says whether that email is already a doola customer.