Reading a Feature Flag on Your Server

How your backend decides a code flag with the Pertento server SDK or over HTTP, what a decision tells you, how exposures are counted, and how to seed the page so…

3 min read


Your backend can decide a code flag with the Pertento server SDK, @pertentoai/sdk, or by calling the HTTP endpoint directly. The SDK downloads the website's setup and decides locally, so a decision costs no network call. See Installing the Pertento Server SDK for the install and the client.

Which SDK version

Flags need SDK 0.2.0 or later. The current release is 0.3.0. An older SDK on a website with a code flag answers every key with the default and the reason unknown-key until it is upgraded, and that includes the website's server-side experiments. Upgrade before you publish your first flag.

Decide the flag

The flag's Setup tab shows this snippet with your website ID and key filled in:

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

const pertento = createPertento({
  websiteId: 123,
  serverKey: process.env.PERTENTO_SERVER_KEY,
});
await pertento.ready();

// The visitor ID from your session, or a new one. Store a new one and send it back
// on every request: a different ID is a different visitor.
const visitorId = session.visitorId ?? pertento.createVisitorId();
session.visitorId = visitorId;
const decision = await pertento.getVariant('new-checkout', { visitorId });

if (decision.variantKey === 'on') {
  // On: the new path. decision.payload holds the On payload, if any.
} else {
  // Off: what you do today.
}

A flag's sides have the keys off and on. For a flag that buckets on Customer ID, pass the customer's ID instead of a visitor ID: getVariant('new-checkout', { id: customer.id }).

Keep the server key on the server. See Server Keys: Creating, Rotating and Revoking.

What a decision tells you

Every decision has enrolled, variantKey and payload. A visitor who gets Off because of a rule rather than the rollout carries a reason:

  • targeting: the server rules did not match the attributes you passed. Off.
  • ring: a ring decided the side. enrolled is false, because ring members are not part of the comparison.
  • no-id: no visitor ID or customer ID was given.
  • wrong-id-kind: the flag buckets on the other kind of ID than the one you passed.
  • unknown-key: no flag has this key, or the SDK has no setup yet.
  • not-running: the flag is not live.

Server rules and rings reach the SDK within about 45 seconds of a change. See Who Gets a Feature Flag.

Exposures: how a server decision is counted

A flag decided only on the server, with no seeded page, is counted by the SDK itself from 0.3.0. The SDK reports its decisions in the background, batched every 2 seconds. flush() sends what is waiting, close() flushes and stops, and exposures: false on the client turns reporting off.

Without 0.3.0, a flag decided only on the server has no counts and no guardrail data. Pertento counts a visitor once whether the page or the server reports first.

Reading the same flag on the server and the page

The browser's own bucket is different from the server's, so a visitor the server put On could read Off on the page. If both your server and your page read or apply the flag, decide on the server and seed the page, so both agree:

const seed = pertento.seedScript([decision], visitorId);
// Put the seed in <head>, before the Pertento snippet.

Seed every page that reads or applies the flag. A customer ID flag can only be read in the browser this way. Without a seed it reads Off. The seed is explained in full in Installing the Pertento Server SDK.

Over HTTP, without the SDK

For a backend in another language, ask the decide endpoint directly:

curl -X POST https://tracking.pertento.ai/sdk/decide \
  -H "Authorization: Bearer $PERTENTO_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "keys": ["new-checkout"], "visitorId": "<visitor id>", "attributes": {} }'

The full reference, including the events, exposures and config endpoints, is in The Server-side HTTP API.