Installing the Pertento Server SDK

Learn how to install @pertentoai/sdk on your backend, decide variants locally, pass the decision to the page and report conversions.

4 min read


The server SDK lets your backend decide a Server-side experiment with Assignment: Server. It is available for TypeScript and JavaScript as @pertentoai/sdk on npm, open source under the MIT licence. It runs on Node 20 and later, Bun, Deno and edge workers such as Cloudflare Workers, Vercel Edge and Deno Deploy, and has no dependencies. It is an ES module package, so load it with import.

For a backend in another language, use the HTTP API.

Install

npm install @pertentoai/sdk

Or bun add @pertentoai/sdk, pnpm add @pertentoai/sdk or deno add npm:@pertentoai/sdk.

Create one client per process

import { createPertento } from '@pertentoai/sdk';

const pertento = createPertento({
  websiteId: Number(process.env.PERTENTO_WEBSITE_ID),
  serverKey: process.env.PERTENTO_SERVER_KEY,
});

await pertento.ready();

The client downloads the website's experiment setup and refreshes it every 30 seconds in the background, so each decision is made locally with no network call. ready() is optional: it waits up to 5 seconds for the first download and resolves true or false. It never throws, so a slow network at startup cannot stop your server.

Keep the server key in an environment variable on the server and never send it to a browser. See Server keys: creating, rotating and revoking.

On each request

// 1. Who is this? Reuse the ID you stored, or make one on the first request.
let visitorId = session.pertentoVisitorId;
if (!visitorId) {
  visitorId = pertento.createVisitorId();
  session.pertentoVisitorId = visitorId;
}

// 2. Decide.
const decision = await pertento.getVariant('new-checkout', { visitorId, attributes: { plan: user.plan } });
if (decision.variantKey === 'one-page') {
  // render the one-page checkout; decision.payload is the variant's JSON payload
}

// 3. Pass the decision to the page.
const seed = pertento.seedScript([decision], visitorId);

To decide every experiment at once, getAll({ visitorId }) returns a decision per flag key, and seedScript accepts the result as it is.

Put the seed before the Pertento script

seedScript() writes a small script that sets window.PertentoSeed. It must be in the head, before the Pertento snippet:

<head>
  <script>window.PertentoSeed={"visitorId":"…","assignments":{"501":{"variantId":"9002"}}};</script>
  <script src="https://scripts.pertento.ai/…"></script>
</head>

The snippet reads the seed, enrols the visitor in exactly the variants your server chose and counts them under the same visitor ID. Without the seed, the visitor sees the variant but is never counted. If your Content-Security-Policy needs a nonce, pass it as seedScript(decisions, visitorId, { nonce }).

Carry the visitor ID between requests

Pertento sets no cookies, so your server remembers who the visitor is. Store the ID the first time you create it, in your session or a first-party cookie you set, and send the same ID on every request after that. A request without it is a new visitor: bucketed again, with their conversions landing on unrelated variants.

A single-page app that calls your backend itself can send the browser's ID instead, read from window.Pertento.visitorId, in the header x-pertento-visitor-id. Treat it as client input: a value that is not a Pertento visitor ID is ignored and the decision says no-id, so make a new ID for that visitor.

What a decision tells you

Every decision has enrolled, variantKey, variantId and payload. A visitor who is not enrolled gets control, the variant with the lowest ID, and a reason:

  • unknown-key: no experiment has this flag key, or the SDK has no setup yet. Everything is empty, so render your default.
  • not-running: the experiment is not running. A deployed experiment returns its deployed variant.
  • no-id: no visitor ID or customer ID was given.
  • wrong-id-kind: the experiment buckets on the other kind of ID than the one you passed.
  • targeting: the attribute rules did not match the attributes you passed.

Edge workers

Request-scoped runtimes keep no timers between requests. Create the client with background: false, and wait for it in the handler with a short timeout:

pertento ??= createPertento({ websiteId, serverKey, background: false });
await pertento.ready(1000); // false without a setup: decisions fall back to control

When something goes wrong

Nothing after createPertento throws into your request path. A network failure, a refused key or an outage leaves decisions on the last setup the client had, or on control if it never had one. The reason is in pertento.lastError, and it is logged once per kind of failure rather than once per request.

A key created moments ago can be refused for up to about 30 seconds, and the SDK retries it for 2 minutes before giving up.

Reporting conversions

With the website's tracking method set to Server-side, report conversions with pertento.track(). See Reporting conversions from your server.

Further reading

The full reference, with every option, customer IDs and the error table, is in the SDK's README on GitHub and npm.