AffiliateRail docs
APIServer-side attribution

Node

Read the referral cookie and attribute a Stripe customer from any Node handler.

Works with Express, Fastify, Hono, or a plain http handler: anything that gives you the request's cookies. The three lines are the ones that read the cookie and pass the click id.

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. */
function railReferral(cookieHeader) {
  const match = /(?:^|;\s*)rail_referral=([^;]*)/.exec(cookieHeader ?? "");
  if (!match) return null;
  try {
    return JSON.parse(decodeURIComponent(match[1])).clickId ?? null;
  } catch {
    return null;
  }
}

app.post("/signup", async (req, res) => {
  const clickId = railReferral(req.headers.cookie);

  const customer = await stripe.customers.create({
    email: req.body.email,
    metadata: clickId ? { rail_referral: clickId } : {},
  });

  // Elements, a PaymentIntent, a Subscription: whatever you create next for this customer is
  // attributed, because the customer is. Nothing else to pass.
  res.json({ customerId: customer.id });
});

If you create the subscription before the customer has the metadata (some flows create the customer early, at account signup, and the referral arrives later), put it on the subscription instead:

await stripe.subscriptions.create({
  customer: customer.id,
  items: [{ price: priceId }],
  metadata: clickId ? { rail_referral: clickId } : {},
});

Both work. Both are idempotent: the first one we see binds the customer, the second is ignored.