Logo

Server-Side Conversions

Connect the conversions your server records to the marketing that brought the visitor to your site

Some conversions never happen in the browser. An OAuth callback creates the account, a payment provider's webhook confirms the purchase, a background job marks the trial as started. Your server knows the conversion happened, but the marketing that caused it was only ever visible in the visitor's browser: the newsletter link they clicked, the campaign in the URL, the session they were in.

This guide shows how to carry that browser-side knowledge to your server, so a server-recorded event is attributed the same way a browser event would be.

Before you begin:

How the Browser Hands Off What It Knows

The browser SDK keeps a small record of the current visitor: which device and session they are in, who they are if a tagged link or people.upsert() has identified them, the page that referred them, and the utm_* and qt_* attribution from the URL they arrived on. Quotient calls this record the browser context. It reaches your server in one of two ways.

The qt_browser_context cookie. On every tracked event, the SDK writes the browser context to a first-party cookie named qt_browser_context. The browser attaches it to every request to your own domain, including requests that no page of yours sends, such as an OAuth callback. Read it when the request that completes the conversion is a request to your own domain.

The cookie expires 30 minutes after the visitor's last tracked event, the same lifetime as the analytics session. If the cookie is present, the session it describes is still live.

client.getBrowserContext(). Cookies do not travel to other domains. When your conversion request goes to an API on a different domain, ask the browser SDK for the same record and send it in your request body:

const browserContext = client.getBrowserContext();

await fetch("https://api.yourcompany.com/signup", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ ...formData, browserContext }),
});

What the Browser Context Contains

Both paths give you the same object:

type BrowserContext = {
  version: 1;
  deviceId?: string;     // random ID minted the first time this browser loaded the SDK
  sessionId: string;     // the rolling 30-minute session
  personId?: string;     // set once a tagged link or people.upsert() identified the visitor
  referrerUrl?: string;  // the external page that sent the visitor
  attribution: Attribution; // utm_* and qt_* parameters from the arrival URL; {} when untagged
};

The cookie value is encodeURIComponent(JSON.stringify(browserContext)).

This shape is a stable contract. Fields are only ever added while version stays 1, so it is safe to store the object or forward it through your own systems. If your code sees a version it does not recognize, ignore the object rather than guessing at its contents.

Record the Event on Your Server

Read the cookie (or the body field), parse it, and pass the result as browserContext on the server event. Quotient then stores the event with the same session, device, referrer, and attribution a browser event would carry, so the source, campaign, and content dimensions in your reports include it.

import {
  BROWSER_CONTEXT_COOKIE_NAME,
  type BrowserContext,
} from "@quotientjs/server";

// The cookie is untrusted input. Treat anything unreadable as "no context"
// so a bad cookie can never break your own signup handler.
function readBrowserContext(
  cookieValue: string | undefined,
): BrowserContext | undefined {
  if (!cookieValue) return undefined;
  try {
    return JSON.parse(decodeURIComponent(cookieValue));
  } catch {
    return undefined;
  }
}

// inside your signup handler, after the account is actually created
// (Next.js shown; `cookies.get()` returns an object with a `value`)
const browserContext = readBrowserContext(
  request.cookies.get(BROWSER_CONTEXT_COOKIE_NAME)?.value,
);

await quotient.analytics.event({
  eventType: "custom",
  personId: person.id,
  customEventId: "signedUp",
  browserContext,
});

Send the event after the conversion has actually succeeded, the same rule as for browser events. If the account was not created, there is nothing to attribute.

What Quotient Trusts

The browser context is a claim about attribution, and only that. It comes from the visitor's browser, so Quotient never lets it decide who an event belongs to:

  • personId on the event itself decides the person. browserContext.personId is ignored, and the API still checks that personId belongs to your business.
  • A browserContext the API cannot make sense of (an unknown version, or attribution that breaks the URL-tagging rules) is dropped, and the event is recorded without attribution. A stale or tampered cookie never costs you the conversion itself.

Next Steps