Zum Inhalt springen
Alle Artikel

Idempotente Stripe- und Lemon-Squeezy-Webhooks in Next.js

Wie du rohe Webhook-Signaturen prüfst, wiederholte Zustellungen an der Datenbankgrenze deduplizierst und verhinderst, dass überlappende Abos bezahlten Zugriff entziehen.

das supastack-Team2 Min. Lesezeit

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.

Prüfe den rohen Payload vor dem JSON-Parsing

Jeder Zahlungsanbieter signiert exakt die Bytefolge, die er über die Leitung sendet. Wenn ein Route-Handler await request.json() parst und das Objekt vor der Signaturprüfung erneut serialisiert, machen Abweichungen bei Schlüsselreihenfolge oder Leerzeichen gültige Webhooks ungültig — oder verleiten Entwickler dazu, die Prüfung im Staging abzuschalten.

Lies den rohen Body einmal mit await request.text() in deinem Next.js-App-Router-POST-Handler, prüfe die kryptografische Signatur gegen das serverseitige Webhook-Secret und lehne fehlerhafte Anfragen mit HTTP 400 ab, bevor eine einzige Datenbankabfrage läuft. Der Billing-Modul-Leitfaden zeigt, wie supastack Signaturprüfungen hinter einem zentralen BFF-Einstiegspunkt isoliert.

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 });
}
  • Lies den rohen Request-Body mit request.text() — serialisiere niemals geparstes JSON neu.
  • Halte STRIPE_WEBHOOK_SECRET und Lemon-Squeezy-Signaturgeheimnisse in rein serverseitiger Konfiguration.
  • Nutze zeitkonstanten Vergleich (crypto.timingSafeEqual), wenn du HMAC-Signaturen manuell prüfst.

Dedupliziere an der Datenbankgrenze, nicht im Arbeitsspeicher

Stripe und Lemon Squeezy wiederholen Webhooks, sobald ein Deployment neu startet, eine Serverless-Funktion in ein Timeout läuft oder ein Netzwerk-Hop die Antwort verliert. Prozesslokale Sets oder kurzlebige Cache-Keys verschwinden über Instanzen hinweg und verhindern nicht, dass zwei gleichzeitige Retries doppelte Lizenzen vergeben oder zwei Quittungs-E-Mails senden.

Speichere die Event-ID des Anbieters oder den Abrechnungsstatus der Bestellung in derselben Postgres-Transaktion, die den Abo-Zugriff ändert. Ein atomares INSERT ... ON CONFLICT DO NOTHING oder ein bedingtes Update auf status != 'paid' macht aus einer parallelen Wiederholung einen sauberen No-op. Beim Entwerfen der Tarife hilft der SaaS-Preisseiten-Generator, Stufen und Sitzplatzregeln vorab zu modellieren.

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;
});

Behandle unsortierte Events und überlappende Abonnements

Webhooks kommen nicht garantiert in der Reihenfolge an, in der der Nutzer geklickt hat. customer.subscription.updated kann vor checkout.session.completed eintreffen, und ein Kunde mit zwei offenen Checkout-Tabs kann kurzzeitig zwei Abos im selben Workspace halten.

Binde Kündigungen und Zahlungsfehler an die konkrete Abo-ID, die das Event ausgelöst hat. Prüfe vor dem Entzug einer bezahlten Berechtigung, ob ein anderes aktives Abo denselben Workspace weiterhin abdeckt — sonst entzieht die Kündigung eines doppelten Abos stillschweigend den Zugriff, für den der Kunde weiter bezahlt.

  • Schlüssele den Abo-Status nach providerSubscriptionId, nicht nur nach workspaceId.
  • Halte die bezahlte Berechtigung eines Workspaces aktiv, solange mindestens ein Abo active oder trialing ist.
  • Prüfe vor der Erfüllung von Einmalbestellungen, dass gezahlter Betrag und Währung exakt zur erwarteten SKU passen.

Häufige Fragen zu SaaS-Billing-Webhooks

Diese Fragen tauchen auf, sobald ein Team die Abrechnung von Sandbox-Tests auf echten Produktionsverkehr umstellt.

  • Kann ich bezahlte Funktionen direkt beim Checkout-Success-Redirect freischalten? — Nein. Redirect-URLs können gespeichert, geteilt oder aufgerufen werden, bevor asynchrone Zahlungsmethoden abgeschlossen sind. Nutze die Erfolgsseite zum Abfragen des Serverstatus, während der verifizierte Webhook den Zugriff vergibt.
  • Welchen Statuscode sollte ein doppelter Webhook zurückgeben? — Gib 200 OK zurück, sobald deine Datenbank bestätigt, dass das Event bereits verarbeitet wurde. Ein 4xx oder 5xx weist den Anbieter an, weiter Wiederholungen zu senden.
  • Wie teste ich Webhook-Retries lokal? — Schreibe deterministische Unit-Tests, die exakt denselben signierten Payload zweimal senden, und prüfe, dass nur eine Lizenz, Berechtigung oder Transaktions-E-Mail entsteht.
  • Wie passen mobile In-App-Käufe in dasselbe Modell? — Leite RevenueCat-Serverbenachrichtigungen durch dieselbe idempotente Berechtigungstabelle, damit Web- und Mobile-Käufe sich niemals gegenseitig überschreiben — siehe die Launch-Checkliste für die Prüfungen vor dem Release.

Häufige Fragen

14 Fragen

Was ist supastack?

supastack ist eine modulare Basis zum Bauen von KI-gestützten SaaS-Produkten. Sie bündelt die gemeinsame Infrastruktur, damit du dich auf die Funktionen konzentrieren kannst, die dein Produkt ausmachen.

Unterstützt es Web und Mobil?

Ja. supastack enthält Next.js- und Expo-Apps als Ausgangspunkt für Web- und Mobile-Erlebnisse.

Welche Authentifizierung ist enthalten?

Better Auth deckt E-Mail/Passwort, OTP und Social Login ab. Persönliche Workspaces werden mit dem Konto provisioniert, damit Datensätze vom ersten Tag an begrenzt sind.

Wie wird KI unterstützt?

supastack enthält konfigurierbare Multi-Anbieter-KI, Nutzungsverfolgung, Budgets und verschlüsselte Bring-your-own-key-Unterstützung.

Wie werden Abonnements gehandhabt?

Für Produkte, die auf dem Kit aufbauen, bilden Stripe-Abrechnung, Abonnements und Berechtigungen die Basis für bezahlten Zugang. Anbieter-Geheimnisse bleiben serverseitig.

Wo liegt mein produktspezifischer Code?

Produktspezifische Funktionen bleiben in einem separaten Codebase, während das Produkt die Basismodule von supastack konfiguriert und integriert.

Wie funktioniert die Preisgestaltung von supastack?

Das Kit ist ein Einmalkauf mit lebenslangen Updates: Single Stack für 249 $ — deine Wahl aus dem Next.js- (Web), Expo- (iOS & Android) oder Tauri-Repo (macOS & Windows Desktop) —, Dual Stack für 398 $ mit zwei beliebigen Repos oder Full Bundle für 399 $ mit allen drei Repos und dem vollständigen Monorepo, plus 75 $ pro zusätzlichem Entwickler-Sitz. Kein Kit-Abo. Checkout auf dieser Website liefert deinen Lizenzschlüssel per E-Mail, mit Repo-Zugang per GitHub-Einladung.

Wie begrenzen Workspaces meine Daten?

Nutzerdatensätze sind über workspaceId begrenzt. Identität, Berechtigungen, KI-Nutzung, Abrechnungsereignisse und Audit-Einträge bleiben innerhalb der persönlichen Workspace-Grenze.

Wie funktionieren Hintergrundjobs?

Trigger.dev ist die explizite Job-Laufzeit. Der JobRunner-Vertrag verspricht nur enqueue, status und cancel — Workflow-Semantik bleibt Trigger.dev-spezifisch und explizit.

Wie unterscheidet sich E-Mail von Outreach?

Transaktions-E-Mail deckt Produktabläufe wie Task-Abschluss-Hinweise ab. Cold Outreach und Kampagnen gehören zum Produkt selbst, außerhalb des E-Mail-Moduls dieses Kits.

Wie funktionieren mobile Käufe?

Mobile bietet native Käufe und Wiederherstellung über RevenueCat; dein Server prüft den Zugriff. Konfiguriere die Store-Konten und schließe Gerätetests vor der Veröffentlichung ab. Web-Abrechnung bleibt separat.

Wo liegen die KI-Anbieter-Schlüssel?

BYOK-Zugangsdaten sind verschlüsselt und bleiben serverseitig. Schlüssel werden in Logs bereinigt; Nutzung und Budgets bleiben auf Workspace-Ebene sichtbar.

Welche Zahlungsmethoden akzeptiert der Checkout?

Der von Stripe gehostete Checkout akzeptiert Karten und kann — sobald die Wallets im Stripe-Konto aktiviert sind — auch Alipay und WeChat Pay anbieten, praktisch für Käufer in China. Wallet-Autorisierungen werden asynchron bestätigt, sodass sich die Lizenzseite aktualisiert, sobald die Zahlung eingegangen ist.

Kann ich eine Rückerstattung erhalten?

Sobald du Zugriff auf das Repository hast, gehört supastack für immer dir und kann daher nicht zurückerstattet werden. Aber keine Sorge: Du erhältst den vollständigen Quellcode mit lebenslangen Updates, um unbegrenzt viele Produkte für dich oder deine Kunden zu erstellen und zu veröffentlichen. Du darfst den Code lediglich nicht weiterverkaufen oder das Repository als Starter-Kit oder Vorlage weitergeben. Eine Ausnahme ist der Launch-Cashback — launche ein Live-Produkt, das mit supastack gebaut ist, teile es und erreiche innerhalb von 60 Tagen 5.000 organische Aufrufe oder 50 organische Likes, um 50 % des gezahlten Betrags zurückzuerhalten; siehe die Bedingungen.

DE