Naar inhoud gaan
Alle artikelen

Idempotente Stripe- en Lemon Squeezy-webhooks in Next.js

Hoe je ruwe webhook-handtekeningen verifieert, herhaalde afleveringen ontdubbelt op de databasegrens en voorkomt dat overlappende abonnementen betaalde toegang intrekken.

het supastack-team2 min lezen

How do I handle Stripe and Lemon Squeezy webhooks idempotently in Next.js?

It depends on your billing providers, but the core architecture is identical: verify the raw payload signature before parsing JSON, normalize provider events into a shared internal contract, and enforce idempotency at the database boundary using the provider's event or order ID. Never grant product entitlements from a client-side checkout redirect; let the verified webhook transactionally update subscription state and entitlements once. supastack prewires this pipeline for Stripe, Lemon Squeezy, and mobile RevenueCat, though single-plan apps can start with a simpler handler.

Verifieer de ruwe payload voordat je JSON aanraakt

Elke betalingsprovider ondertekent de exacteeks bytes die over de lijn wordt gestuurd. Als een route-handler await request.json() parseert en het object opnieuw serialiseert voordat de handtekening wordt gecontroleerd, maken verschillen in sleutelvolgorde of witruimte legitieme webhooks ongeldig — of verleiden ze ontwikkelaars om verificatie in staging over te slaan.

Lees de ruwe body één keer uit met await request.text() in je Next.js App Router POST-handler, verifieer de cryptografische handtekening tegen het server-only webhook-geheim en wijs alles wat faalt af met HTTP 400 voordat je ook maar één databasequery uitvoert. De handleiding voor de facturatiemodule laat zien hoe supastack handtekeningcontroles isoleert achter één BFF-toegangspunt.

export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get("stripe-signature");
  if (!signature) return new Response("Missing signature", { status: 400 });

  const event = stripe.webhooks.constructEvent(
    rawBody,
    signature,
    env.STRIPE_WEBHOOK_SECRET,
  );
  await handleNormalizedBillingEvent(event);
  return Response.json({ received: true });
}
  • Lees de ruwe request-body met request.text() — serialiseer geparseerde JSON nooit opnieuw.
  • Bewaar STRIPE_WEBHOOK_SECRET en Lemon Squeezy-ondertekeningsgeheimen in server-only configuratie.
  • Gebruik vergelijking in constante tijd (crypto.timingSafeEqual) wanneer je HMAC-handtekeningen handmatig verifieert.

Ontdubbel op de databasegrens, niet in het geheugen

Stripe en Lemon Squeezy proberen webhooks opnieuw zodra een deployment herstart, een serverless-functie een time-out krijgt of een netwerk-hop het antwoord verliest. Proces-lokale sets of kortlevende cachesleutels verdwijnen tussen instanties en houden niet tegen dat twee gelijktijdige retries dubbele licenties uitgeven of twee ontvangstbewijzen mailen.

Leg het event-ID van de provider of de afwikkelingsstatus van de bestelling vast binnen dezelfde Postgres-transactie die de abonnementstoegang wijzigt. Een atomaire INSERT ... ON CONFLICT DO NOTHING of een voorwaardelijke update op status != 'paid' maakt van een gelijktijdige herhaling een veilige no-op. Bij het ontwerpen van de plannen die deze webhooks afhandelen, helpt de SaaS-prijspaginagenerator om vooraf pakketten en stoelregels te modelleren.

const settled = await db.transaction(async (tx) => {
  const [inserted] = await tx
    .insert(billingWebhookEvents)
    .values({ providerEventId: event.id, provider: "stripe" })
    .onConflictDoNothing({ target: billingWebhookEvents.providerEventId })
    .returning({ id: billingWebhookEvents.id });

  if (!inserted) return false; // Already processed
  await applyWorkspaceEntitlement(tx, event);
  return true;
});

Vang events buiten volgorde en overlappende abonnementen op

Webhooks komen niet gegarandeerd binnen in de volgorde waarin de gebruiker heeft geklikt. customer.subscription.updated kan vóór checkout.session.completed arriveren, en een klant die per ongeluk twee afrekentabbladen opent kan kortstondig twee abonnementen in dezelfde workspace hebben.

Koppel opzeggingen en mislukte betalingen aan het specifieke abonnements-ID dat het event veroorzaakte. Controleer vóór het intrekken van het betaalde recht van een workspace of een ander actief abonnement die workspace nog steeds dekt — anders verwijdert het opzeggen van een dubbel abonnement stilzwijgend de toegang waarvoor de klant nog steeds betaalt.

  • Sleutel abonnementsstatus op providerSubscriptionId, niet alleen op workspaceId.
  • Houd het betaalde recht van een workspace actief zolang ten minste één abonnement active of trialing is.
  • Controleer of het betaalde bedrag en de valuta overeenkomen met de verwachte SKU voordat je eenmalige bestellingen uitlevert.

Veelgestelde vragen over SaaS-facturatiewebhooks

Deze vragen komen naar boven zodra een team de facturatie van sandbox-tests naar productie-verkeer brengt.

  • Kan ik betaalde functies ontgrendelen bij de succes-redirect van de checkout? — Nee. Redirect-URL's kunnen worden gebookmarkt, gedeeld of bezocht voordat asynchrone betaalmethoden zijn afgewikkeld. Gebruik de succespagina om de serverstatus te pollen terwijl de geverifieerde webhook toegang verleent.
  • Welke statuscode moet een dubbele webhook teruggeven? — Geef 200 OK terug zodra je database bevestigt dat het event al is verwerkt. Een 4xx of 5xx teruggeven vertelt de provider om te blijven proberen.
  • Hoe test ik webhook-retries lokaal? — Schrijf deterministische unittests die exact dezelfde ondertekende payload twee keer versturen en controleer dat er maar één licentie, recht of transactionele e-mail wordt aangemaakt.
  • Hoe passen mobiele in-app-aankopen in hetzelfde model? — Routeer RevenueCat-servermeldingen via dezelfde idempotente rechtentabel zodat web- en mobiele aankopen elkaar nooit overschrijven — bekijk de lenchecklist voor de controles vóór livegang.

Veelgestelde vragen

14 vragen

Wat is supastack?

supastack is een modulair fundament voor het bouwen van SaaS-producten met AI. Het brengt gemeenschappelijke infrastructuur samen zodat jij je kunt richten op productspecifieke functies.

Ondersteunt het web en mobiel?

Ja. supastack bevat Next.js- en Expo-apps als startpunten voor web- en mobiele ervaringen.

Welke authenticatie is inbegrepen?

Better Auth dekt e-mail/wachtwoord, OTP en sociale login. Persoonlijke workspaces worden bij het account geprovisioneerd zodat productrecords vanaf dag één afgebakend blijven.

Hoe ondersteunt het AI?

supastack bevat configureerbare multi-provider AI, usage-tracking, budgetten en versleutelde bring-your-own-key-ondersteuning.

Hoe worden abonnementen afgehandeld?

Voor producten op de kit vormen Stripe-billing, abonnementen en entitlements het fundament voor betaalde toegang. Providersecrets blijven server-only.

Waar leeft mijn productspecifieke code?

Productspecifieke functies blijven in een aparte codebase terwijl het product de fundamentmodules van supastack configureert en integreert.

Hoe werkt de prijs van supastack?

De kit is een eenmalige aankoop met lifetime updates: Single Stack voor $249 — jouw keuze uit de Next.js- (web), Expo- (iOS & Android) of Tauri-repo (macOS & Windows desktop) —, Dual Stack voor $398 met twee repo's naar keuze, of de Full Bundle voor $399 met alle drie de repo's en het volledige monorepo, plus $75 per extra developer seat. Geen kit-abonnement. Checkout op deze site levert je licentiesleutel per e-mail, met repositorytoegang via een GitHub-uitnodiging.

Hoe bakens workspaces mijn data af?

Records van gebruikers worden afgebakend per workspaceId. Identiteit, entitlements, AI-gebruik, billing-events en auditentries blijven binnen de grens van de persoonlijke workspace.

Hoe werken achtergrondjobs?

Trigger.dev is de expliciete jobruntime. Het JobRunner-contract belooft alleen enqueue, status en cancel — workflowsemantiek blijft Trigger.dev-specifiek en expliciet.

Hoe verschilt e-mail van outreach?

Transactionele e-mail dekt productworkflows zoals voltooiingsnotices. Koude outreach en campagnes zijn productzaken buiten de e-mailmodule van deze kit.

Hoe werken mobiele aankopen?

Mobiel bevat native aankopen en herstel via RevenueCat, met toegang die je server verifieert. Configureer de winkelaccounts en rond apparaattests af vóór publicatie. Webfacturatie blijft afzonderlijk.

Waar leven de AI-providerkeys?

Bring-your-own-key-credentials zijn versleuteld en blijven server-only. Keys worden uit logs gefilterd; gebruik en budgetten blijven zichtbaar op workspaceniveau.

Welke betaalmethoden accepteert de checkout?

De door Stripe gehoste checkout accepteert kaarten en kan ook Alipay en WeChat Pay aanbieden zodra die wallets op het Stripe-account zijn geactiveerd — handig voor kopers in China. Wallet-autorisaties worden asynchroon bevestigd, dus de licentiepagina wordt bijgewerkt zodra de betaling is afgerond.

Kan ik een terugbetaling krijgen?

Zodra je toegang hebt gekregen tot de repository, is supastack voor altijd van jou en kan er dus geen terugbetaling plaatsvinden. Maar wees gerust: je krijgt de volledige broncode met levenslange updates om onbeperkt producten voor jezelf of je klanten te bouwen en te lanceren. Je mag de code alleen niet doorverkopen of de repository herdistribueren als starterkit of template. Eén uitzondering is de lanceringscashback — lanceer een live product gebouwd met supastack, deel het en haal binnen 60 dagen 5.000 organische weergaven of 50 organische likes om 50% van je betaling terug te krijgen; zie de voorwaarden.

NL