APIServer-side attribution
Next.js
Attribute a Stripe customer from a server action with the App Router.
In a server action or a route handler, cookies() from next/headers gives you the cookie;
the rest is the same three lines.
"use server";
import { cookies } from "next/headers";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
/** The click id from the `rail_referral` cookie, or null when the visitor had no referral. */
async function railReferral(): Promise<string | null> {
const raw = (await cookies()).get("rail_referral")?.value;
if (!raw) return null;
try {
return (JSON.parse(decodeURIComponent(raw)) as { clickId?: string }).clickId ?? null;
} catch {
return null;
}
}
export async function startSubscription(formData: FormData) {
const clickId = await railReferral();
const customer = await stripe.customers.create({
email: String(formData.get("email")),
metadata: clickId ? { rail_referral: clickId } : {},
});
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: process.env.STRIPE_PRICE_ID! }],
payment_behavior: "default_incomplete",
expand: ["latest_invoice.payment_intent"],
});
// Hand the client secret to Stripe Elements on the client; the sale is attributed when the
// invoice pays, because the customer carries the referral.
const invoice = subscription.latest_invoice as Stripe.Invoice & { payment_intent: Stripe.PaymentIntent };
return { clientSecret: invoice.payment_intent.client_secret };
}The cookie is set on your registrable domain, so a marketing site on www.acme.com and an app
on app.acme.com share it without any handoff. If the app lives on a different registrable
domain (getacme.io), list it in the script tag's data-domains attribute on the marketing site
and the script carries the referral across in the link.