Skip to main content
@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. A subscription_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.
Create the client at module scope — one per process, reused across requests — then 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:
Send both ids whenever you have them. Trevo links an anonymous journey to a user the first time it sees an event carrying both. A backend that only ever sends 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. Webhooks and cron jobs have no cookie to read. State the link directly:
Idempotent and safe to retry. An 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 stable insertId derived from the thing that happened:
Trevo records that event once regardless of how many deliveries arrive.

Serverless

Timers do not survive between invocations, so flush explicitly before returning:
For long-running processes, call 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:
Or in code:
Forced reads never record an exposure, and while a force is pinned every event the client sends is marked 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:

Options

API summary