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:
- Install Quotient website tracking on your site. The browser SDK is what collects the attribution in the first place.
- Install
@quotientjs/serverand create a private API key with Analytics: write permission. See the server analytics API for the event call itself. - Create the custom event in Quotient. Server events use the same registered event IDs as browser events.
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:
personIdon the event itself decides the person.browserContext.personIdis ignored, and the API still checks thatpersonIdbelongs to your business.- A
browserContextthe API cannot make sense of (an unknownversion, 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.