Use this when your server creates the checkout session — the usual shape for a custom pricing page.
The visitor id is in a cookie your server can read, so it goes in as metadata
when the session is created, before the customer sees a checkout at all.
// app/api/checkout/route.js
import { Polar } from "@polar-sh/sdk";
import { cookies } from "next/headers";
const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN });
export async function POST() {
// `cookies()` is only readable on the server — this is why the checkout
// session has to be created here and not in the browser.
//
// It is asynchronous from Next.js 15 on. Version 14 returns it directly,
// so drop the `await` there.
const cookieStore = await cookies();
const ts_vid = cookieStore.get("ts_vid")?.value;
const ts_vs = cookieStore.get("ts_vs")?.value;
// Left out entirely when absent: a visitor with cookies blocked has none,
// and an empty string would store an id that matches no visitor.
const metadata = {};
if (ts_vid) metadata.ts_vid = ts_vid;
if (ts_vs) metadata.ts_vs = ts_vs;
const checkout = await polar.checkouts.create({
products: ["PRODUCT-UUID"],
successUrl: "https://yoursite.com/thanks",
metadata,
});
return Response.json({ url: checkout.url });
}Why this is the best of the three
The ids are recorded at the moment of purchase, by your own server, from a cookie the browser cannot forge. Nothing depends on the customer coming back to your site afterwards.
It also carries forward: the metadata stays on the subscription, so every renewal for the life of that subscription is attributed to the visit that won the customer — not just the first payment.
And Polar keeps it on your history
This is the one place Polar does better than every other provider we support. Its API returns checkout metadata on a listed order, so when you connect, imported payments arrive already attributed — as far back as your records go.
Stripe, Paddle and LemonSqueezy all lose that on import: their listed objects carry no metadata, so history lands under Unattributed and attribution starts from the first payment after connecting.
If everything arrives unattributed
Check you are reading the cookie at the moment the customer clicks rather than when the page was built — on a first visit the tag may not have run yet.
If you are creating the checkout from the browser rather than your server, the cookie is not readable there. Use the checkout link instead.