@trevosdk/node runs experiments and records conversions from your backend. It uses
web-standard APIs only — fetch, AbortSignal, timers — so it runs on Node 18+, Bun, Deno,
and edge runtimes.
Why use it
Conversions that cannot be blocked. Asubscription_started fired from a Stripe webhook
is never lost to an ad blocker, a closed tab, or a failed beacon. This improves the numbers
for your browser experiments too, which is the most common reason to adopt it before
running a single backend experiment.
Experiments only a backend can run — pricing and packaging, search ranking, prompt or
model choices, lifecycle email content. The variant is a code path, not a UI element.
Properties the browser shouldn’t be trusted with — plan, seat count, MRR.
Create a client
You need a server key:tsk_secret_…, from Settings → SDK keys with platform Server.
It is shown once. Never put it in client code — secret keys sent with an Origin header are
rejected.
await trevo.ready() inside your request or startup lifecycle. Starting network work inside
the lifecycle also works in edge runtimes that forbid it during module evaluation.
Identity is passed per call
A server holds no ambient user state, so every call takes the identity explicitly:userId never
creates that link, and the user’s pre-login activity is never attached to their conversion.
Bucket with the identity the browser is using at that moment — the user id when
identified, otherwise the trevo_id cookie. Anything else assigns the same person different
variants on the client and the server.
Linking identities without a cookie
Webhooks and cron jobs have no cookie to read. State the link directly:anonymousId belongs to one user — pointing it at a
different user changes nothing and throws TrevoSDKError (HTTP 409). Retrying can’t
succeed — catch it rather than letting your queue replay.
Idempotency
Payment providers replay webhooks, and a conversion counted twice inflates whichever variant that user was in. Pass a stableinsertId derived from the thing that happened:
Serverless
Timers do not survive between invocations, so flush explicitly before returning:shutdown() before exit — it stops timers and flushes once.
Cloudflare Workers expose secrets through the request’s env binding rather than
process.env. Cache construction at module scope, but read the binding and start network work
inside fetch:
Local development
Pin variants without touching Trevo:preview, which every experiment read excludes — so your local testing never
pollutes results, conversions included. A force naming a variant the experiment
doesn’t have is reported through onError rather than ignored quietly.
To render for a browser previewing with ?trevo_force=, pass that request’s force — it wins
over the process-wide one — and mark the events that request produces: