AffiliateRail docs
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.