Integration scaffolder

Add referrals to your stack, wired correctly

Pick your framework and auth provider. Get the exact server-side capture code, with the correct idempotency key so retries never double-count, plus a prompt you can hand to your coding agent.

Framework
Auth provider
Style
Next + Clerk
// simplereferral referral attribution for Next (App Router) + Clerk (fetch mode).
// simplereferral API contract (private beta):
//   POST ${SIMPLEREFERRAL_BASE_URL}/v1/signups
//   Authorization: Bearer <SIMPLEREFERRAL_API_KEY>   (server-side only)
//   body { externalUserId, code }   Idempotency-Key: signup-<externalUserId>
//   Ignore code_not_found | self_referral | already_attributed (never block signup)
//   Access: private beta. Get an early-access API key at https://simplereferralapp.com

// 1. Shared capture helper (server-side only)
// Shared capture helper. Server-side only, never ship the API key to a client.
// The Idempotency-Key derives from the account id (signup-<id>): it is stable
// per account, so a double-fired signup handler replays instead of double
// counting. Never key on a timestamp or a random value.
async function captureReferral(externalUserId, code) {
  if (!code) return // no ref present, nothing to attribute
  try {
    const res = await fetch(`${process.env.SIMPLEREFERRAL_BASE_URL}/v1/signups`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.SIMPLEREFERRAL_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': `signup-${externalUserId}`,
      },
      body: JSON.stringify({ externalUserId, code }),
    })
    if (!res.ok) {
      const err = await res.json().catch(() => ({}))
      // Swallow expected referral edge cases so a bad or absent ref never
      // blocks signup: code_not_found, self_referral, already_attributed.
      const swallow = ['code_not_found', 'self_referral', 'already_attributed']
      if (swallow.includes(err.error?.code)) return
    }
  } catch {
    // network error: never block signup
  }
}

// 2. Capture the ?ref= code across the auth round trip (Next (App Router))
// middleware.ts: capture ?ref= into a cookie that survives the auth round trip.
import { NextResponse } from 'next/server'

export function middleware(request) {
  const ref = request.nextUrl.searchParams.get('ref')
  const res = NextResponse.next()
  if (ref) res.cookies.set('sr_ref', ref, { path: '/', maxAge: 60 * 60 * 24 * 30 })
  return res
}

// 3. Fire capture when Clerk creates the account
// Mount as a Route Handler at app/api/webhooks/clerk/route.ts, verifying the Svix signature first.
// Clerk fires attribution on the user.created webhook (the server-side
// signal for a new account). Forward the ref code through unsafeMetadata at signup:
//   signUp.create({ ...fields, unsafeMetadata: { sr_ref: srRefFromCookie } })
async function onClerkUserCreated(request) {
  // verifyClerkWebhook: implement with svix or @clerk/backend verifyWebhook
  const evt = await verifyClerkWebhook(request) // Svix-verified Clerk event
  if (evt.type !== 'user.created') return
  const externalUserId = evt.data.id
  const code = evt.data.unsafe_metadata?.sr_ref
  await captureReferral(externalUserId, code)
}

Clerk fires attribution on the server-side user.created webhook, not a client callback. Forward the ref code through unsafeMetadata at signup and read it back in the webhook.

powered by simplereferral

One API key. Two calls. Referrals your agent gets right.

simplereferral owns the referral loop up to money: mint links, capture signup attribution, and send invites from shared infrastructure. Get an early-access key.

You’re on the list. We’ll email when the beta opens.