Skip to main content
Better Auth runs inside your app and against your own database. UserKit needs to know which of your users is on the page, and it will not take the page’s word for it — your server signs the claim.
The identity secret never enters the bundle. Never prefix it with NEXT_PUBLIC_, never render it into HTML, never send it to the browser to save a round trip. Whoever holds it can mint a verified session for any of your users.
The contract is the one on the federated identity page and nothing here changes it:

What Better Auth supplies

One value: session.user.id. That is the external_id.
lib/better-auth.ts
getSession validates against the database rather than decoding the cookie. Here the temptation is at its strongest, because the session table is right there — but an id the browser could choose is not something to sign.

The server half

app/api/userkit-boot/route.ts

The client half

getState().verified is true only when the HMAC checked out. Signed in to Better Auth with verified: false means either the secret belongs to the other environment or the message signed was not exactly the external_id.

Configuration

What does not travel

Better Auth’s user carries email and emailVerified. They are statements about your database, and they do not become identities in UserKit: an email sent through /v1/boot is stored as an attribute, does not resolve to an existing contact and does not become an identity edge. The HMAC proves the external_id and only the external_id. To prove an address, send a magic link or an email code — the two flows that arrive in it.

The full example

A runnable Next app with these files, an .env.example and the pieces this page leaves out.