Back to guides
StripeIntermediate45 minutes

Stripe affiliate tracking with RefCampaign

Connect Stripe Checkout, subscriptions, and one-time payments to RefCampaign so paid conversions become attributable commissions.

3 min read

Stripe can carry RefCampaign attribution through metadata. The critical detail is where that metadata lives: subscription metadata for recurring revenue, Payment Intent metadata for one-time payments.

This guide assumes your frontend already captures the RefCampaign session with the browser SDK or CDN script.

Prerequisites

  • A Stripe account with Checkout enabled.
  • A RefCampaign secret key.
  • The @refcampaign/sdk package installed on your backend.
  • A server route that creates Stripe Checkout sessions.
pnpm add @refcampaign/sdk stripe

Store the secrets server-side only:

REFCAMPAIGN_SECRET_KEY=sk_live_your_refcampaign_secret
STRIPE_SECRET_KEY=sk_live_your_stripe_secret
APP_URL=https://yourapp.com

Initialize the server SDK

Create a small module that your checkout routes can import.

// lib/billing.ts
import Stripe from 'stripe'
import { RefCampaignServer } from '@refcampaign/sdk'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export const refcampaign = new RefCampaignServer(
  process.env.REFCAMPAIGN_SECRET_KEY!
)

Subscription Checkout

For recurring products, put the RefCampaign metadata on subscription_data.metadata. This lets renewals keep crediting the same affiliate.

// app/api/stripe/checkout-subscription/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { refcampaign, stripe } from '@/lib/billing'

export async function POST(request: NextRequest) {
  const { priceId, customerEmail } = await request.json()
  const sessionId = request.cookies.get('_rc_sid')?.value

  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    customer_email: customerEmail,
    line_items: [{ price: priceId, quantity: 1 }],
    subscription_data: {
      metadata: refcampaign.getStripeMetadata(sessionId),
    },
    success_url: `${process.env.APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.APP_URL}/pricing`,
  })

  return NextResponse.json({ url: session.url })
}

One-time Checkout

For one-time products, put the metadata on payment_intent_data.metadata.

// app/api/stripe/checkout-payment/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { refcampaign, stripe } from '@/lib/billing'

export async function POST(request: NextRequest) {
  const { priceId, customerEmail } = await request.json()
  const sessionId = request.cookies.get('_rc_sid')?.value

  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    customer_email: customerEmail,
    line_items: [{ price: priceId, quantity: 1 }],
    payment_intent_data: {
      metadata: refcampaign.getStripeMetadata(sessionId),
    },
    success_url: `${process.env.APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.APP_URL}/pricing`,
  })

  return NextResponse.json({ url: session.url })
}

Verify webhook coverage

RefCampaign processes Stripe-side revenue events through your merchant integration. Your job is to make sure the metadata exists before Stripe emits the paid event.

Use Stripe CLI to inspect a test checkout:

stripe listen --forward-to localhost:3000/api/webhooks/stripe
stripe trigger checkout.session.completed

For a real manual test, create a checkout through your UI after clicking an affiliate link. In the Stripe Dashboard:

  • open the Checkout Session;
  • open the Subscription or Payment Intent;
  • verify refcampaign_session appears in metadata.

Handle missing sessions safely

Not every buyer comes from an affiliate. getStripeMetadata() is safe to call with an undefined session ID, and checkout should continue normally.

const metadata = refcampaign.getStripeMetadata(sessionId)

const subscriptionData =
  Object.keys(metadata).length > 0
    ? { metadata }
    : undefined

Use this pattern if your Stripe helper rejects empty metadata objects.

Common mistakes

Do not put subscription attribution on Customer metadata only. Customer metadata is shared across future purchases and can credit the wrong affiliate when a customer re-subscribes later.

Do not attach one-time payment attribution to the Checkout Session only. RefCampaign needs metadata on the Stripe object that represents the paid event.

Do not log RefCampaign secret keys or full Stripe request bodies in production. Metadata is safe to inspect, secrets are not.

Next steps

Pair this setup with the Next.js affiliate tracking guide if your checkout lives in App Router. For broader implementation context, read how affiliate tracking works and keep the SDK reference nearby during review.