Stripe -Webhooks in PHP: ein zuverlässiger Handler, dem Sie vertrauen können
Ein Stripe -Webhook, der auf Ihrem Laptop funktioniert, ist noch kein Webhook, dem Sie Geld anvertrauen können. Diese Anleitung zeigt, wie Sie Signaturen überprüfen, schnell bestätigen, Ereignisse deduplizieren und Wiederholungsversuche in einfachem PHP und Laravel handhaben, mit Code, den Sie in ein Projekt einfügen können.
Kurz gesagt
- Betrachten Sie den Webhook und nicht die Browser-Weiterleitung als Quelle der Wahrheit für den Zahlungsstatus.
- Lesen Sie den rohen Anforderungstext und überprüfen Sie den
Stripe-Signature-Header, bevor Sie die Nutzlast berühren. - Geben Sie schnell eine 2xx-Antwort zurück und erledigen Sie die eigentliche Arbeit in einer Warteschlange oder nach dem Speichern des Ereignisses.
- Speichern Sie die Ereignis-ID mit einem eindeutigen Index: Stripe liefert möglicherweise dasselbe Ereignis mehr als einmal und nicht in der richtigen Reihenfolge.
- Testen Sie mit der Stripe CLI (
stripe listenundstripe trigger), bevor Sie live gehen.
Wenn Sie Zahlungen mit Stripe akzeptieren, entscheiden Stripe Webhooks in PHP darüber, ob Bestellungen als bezahlt markiert werden, Abonnements aktiviert werden und Rückerstattungen in Ihrer Datenbank angezeigt werden. Die Checkout-Seite ist nur die Eingangstür: Kunden schließen Tabs, Banken bestätigen verspätet und einige Zahlungsmethoden führen die Abwicklung erst Minuten oder Tage später durch. Der Webhook ist die Nachricht von Stripe, die sagt, was wirklich passiert ist.
Wir erstellen und pflegen Zahlungsintegrationen für Kunden und für unsere eigenen Produkte (zum Beispiel Bill, das Stripe und PayPal Zahlungen auf einer öffentlichen Bezahlseite entgegennimmt). Dieser Artikel ist die Checkliste, die wir befolgen, wenn wir einen Stripe Webhook-Endpunkt in PHP schreiben.
So funktionieren Stripe Webhooks in einer Minute
Sie registrieren eine HTTPS-URL im Stripe Dashboard (oder über die API) und wählen aus, welche Ereignistypen Sie erhalten möchten. Wenn etwas passiert – eine Checkout-Sitzung abgeschlossen wird, eine Rechnung bezahlt wird, eine Gebühr zurückerstattet wird – sendet Stripe eine POST Anfrage mit einem JSON - Ereignisobjekt an diese URL. Ihr Server muss mit einem 2xx-Statuscode antworten. Ist dies nicht der Fall, versucht Stripe im Live-Modus bis zu drei Tage lang einen erneuten Versuch mit exponentiellem Backoff.
Drei Fakten prägen das gesamte Design:
- Ereignisse können mehr als einmal eintreffen. Wiederholungsversuche und Netzwerkprobleme machen Duplikate normal.
- Ereignisse können außerhalb der Reihenfolge eintreffen. Möglicherweise wird
invoice.paidvorcustomer.subscription.createdangezeigt. - Jeder kann auf Ihre URL posten. Ohne Signaturprüfung kann ein Angreifer seine eigene Bestellung als bezahlt markieren.
Schritt 1: Installieren Sie das SDK und erstellen Sie den Endpunkt
Verwenden Sie die offizielle Bibliothek stripe/stripe-php und halten Sie den Endpunkt geheim (er beginnt mit whsec_) in einer Umgebungsvariablen, niemals im Repository.
composer require stripe/stripe-php
# .env
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
Jeder Webhook-Endpunkt im Dashboard verfügt über ein eigenes Signaturgeheimnis, und das Geheimnis von stripe listen in der Entwicklung ist wiederum anders. Ein sehr häufiger Fehler beim „Signaturüberprüfung fehlgeschlagen“ besteht einfach darin, dass die falsche Signatur verwendet wird.
Schritt 2. Überprüfen Sie die Signatur auf dem Rohkörper
Stripe unterschreibt jede Anfrage. Der Stripe-Signature Header enthält einen Zeitstempel und eine oder mehrere Signaturen, die als HMAC-SHA256 von timestamp.payload mit Ihrem Endpunktgeheimnis berechnet werden. Die Bibliothek prüft die Signatur und lehnt standardmäßig Ereignisse ab, die älter als fünf Minuten sind.
Das wichtige Detail: Für die Verifizierung sind die genauen Rohbytes erforderlich, Stripe gesendet hat. Wenn ein Framework das JSON analysiert und Sie es neu kodieren, ändern sich die Leerzeichen und die Signatur stimmt nicht mehr überein.
<?php
// public/webhooks/stripe.php (plain PHP)
require __DIR__ . '/../../vendor/autoload.php';
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
$secret = getenv('STRIPE_WEBHOOK_SECRET');
try {
$event = \Stripe\Webhook::constructEvent($payload, $sigHeader, $secret);
} catch (\UnexpectedValueException $e) {
http_response_code(400); // invalid JSON
exit;
} catch (\Stripe\Exception\SignatureVerificationException $e) {
http_response_code(400); // bad or missing signature
exit;
}
Lesen Sie in Laravel $request->getContent() anstelle von $request->all() und schließen Sie die Route vom CSRF-Schutz aus (Webhooks tragen kein CSRF-Token; die Signatur ist der Schutz). Platzieren Sie die Route nach Möglichkeit außerhalb der web-Middleware-Gruppe.
Schritt 3: Machen Sie den Handler idempotent
Idempotent bedeutet: Die zweimalige Verarbeitung desselben Ereignisses führt zum gleichen Ergebnis wie die einmalige Verarbeitung. Der einfachste und zuverlässigste Ansatz ist eine Tabelle verarbeiteter Ereignis-IDs mit einem eindeutigen Index. Zuerst einfügen; Wenn das Einfügen fehlschlägt, weil die ID vorhanden ist, haben Sie das Ereignis bereits gesehen.
CREATE TABLE stripe_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
event_id VARCHAR(64) NOT NULL,
type VARCHAR(100) NOT NULL,
payload JSON NOT NULL,
status ENUM('received','processed','failed') NOT NULL DEFAULT 'received',
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL,
UNIQUE KEY uniq_event (event_id)
);
try {
$pdo->prepare('INSERT INTO stripe_events (event_id, type, payload) VALUES (?, ?, ?)')
->execute([$event->id, $event->type, $payload]);
} catch (PDOException $e) {
if ($e->getCode() === '23000') { // duplicate key: already received
http_response_code(200);
exit;
}
throw $e;
}
Durch das Speichern der Rohnutzlast erhalten Sie einen Prüfpfad: Wenn ein Kunde fragt: „Warum wurde mir zweimal etwas in Rechnung gestellt?“, können Sie genau das wiedergeben, was Stripe Ihnen gesagt hat. Außerdem ist es möglich, fehlgeschlagene Ereignisse erneut zu verarbeiten, nachdem Sie einen Fehler behoben haben.
Schritt 4. Schnell bestätigen, separat verarbeiten
Stripe erwartet eine Antwort innerhalb weniger Sekunden. Wenn Ihr Handler E-Mails sendet, andere APIs aufruft oder PDFs inline generiert, kommt es zu einer Zeitüberschreitung, Stripe wird es erneut versuchen und Sie werden die gleiche Arbeit erneut ausführen. Führen Sie das Minimum in der Anfrage aus – überprüfen, speichern, antworten 200 – und führen Sie die eigentliche Logik in einem Warteschlangen-Worker aus ( Laravel Warteschlangen, Symfony Messenger, ein Cron-Job, der status = 'received' liest, oder ein vom Supervisor verwalteter Worker).
Wenn das Ereignis überhaupt nicht gespeichert werden kann (Datenbank ausgefallen), geben Sie einen 5xx-Status zurück. Dies ist der einzige Fall, in dem Stripe es später noch einmal versuchen soll.
Schritt 5. Behandeln Sie die Ereignisse, die wichtig sind
Sie benötigen nicht jeden Veranstaltungstyp. Abonnieren Sie nur das, was Ihre Geschäftslogik verwendet. Weniger Abonnements bedeuten weniger Lärm und eine kleinere Angriffsfläche. Ein typisches Set:
checkout.session.completed– Der Kunde hat den Checkout abgeschlossen. Suchen Sie Ihre Bestellung anhand derclient_reference_idoder der Metadaten, die Sie beim Erstellen der Sitzung festgelegt haben.payment_intent.succeededundpayment_intent.payment_failed– für benutzerdefinierte Zahlungsflüsse.invoice.paid“ undinvoice.payment_failed– Abonnementabrechnung.customer.subscription.updatedundcustomer.subscription.deleted– Planänderungen und Stornierungen.charge.refundedundcharge.dispute.created“ – Geld geht zurück.
switch ($event->type) {
case 'checkout.session.completed':
$session = $event->data->object;
$orderId = $session->client_reference_id;
if ($session->payment_status === 'paid') {
markOrderPaid($orderId, $session->payment_intent);
}
break;
case 'invoice.payment_failed':
notifyBillingProblem($event->data->object->customer);
break;
case 'charge.refunded':
markOrderRefunded($event->data->object->payment_intent);
break;
default:
// unknown or unsubscribed type: acknowledge and ignore
break;
}
Zwei Gewohnheiten verhindern die meisten Fehler. Vertrauen Sie zunächst niemals Beträgen aus dem Browser: Vergleichen Sie den gezahlten Betrag und die Währung im jeweiligen Fall mit der Bestellung in Ihrer Datenbank. Zweitens: Da Ereignisse außer Betrieb sein können, sollten Statusänderungen monoton erfolgen: Verschieben Sie eine Bestellung nicht von „bezahlt“ zurück auf „ausstehend“, weil ein älteres Ereignis zu spät eingegangen ist. Rufen Sie im Zweifelsfall das aktuelle Objekt von der API ab, anstatt sich auf den Ereignis-Snapshot zu verlassen.
Schritt 6: Lokal mit der Stripe CLI testen
Die Stripe CLI leitet echte Testmodus-Ereignisse an Ihren Computer weiter, sodass Sie keinen öffentlichen Tunnel benötigen:
stripe login
stripe listen --forward-to http://localhost:8000/webhooks/stripe
# in another terminal:
stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed
stripe listen gibt ein temporäres whsec_ Geheimnis aus. Fügen Sie das in Ihre lokale .env ein. Testen Sie auch absichtlich die unglücklichen Pfade: Senden Sie eine Anfrage mit einer falschen Signatur (erwarten Sie 400), senden Sie dasselbe Ereignis zweimal (erwarten Sie einen Datenbankeffekt) und halten Sie Ihren Warteschlangenarbeiter für eine Minute an, um zu sehen, dass sich Ereignisse häufen, anstatt zu verschwinden.
Häufige Fehler, die wir bei Audits sehen
- Verwendung der Weiterleitung als Zahlungsnachweis. Die Seite
success_urlkann von jedem geöffnet werden; Zeigen Sie „Bestätigung Ihrer Zahlung“ an, bis der Webhook eintrifft. - Parsen von JSON vor der Überprüfung. Neu kodierte Körper brechen die Signatur; Framework-Body-Parser sind ein häufiger Übeltäter.
- Ein Geheimnis für Test und Live. Test- und Live-Modus verfügen über separate Schlüssel und separate Webhook-Endpunkte.
- Keine Protokollierung. Ohne gespeicherte Ereignisse und einen sichtbaren Fehlerkanal werden Zahlungsfehler von Kunden entdeckt.
- Langsame Handler. Alles, was länger als ein paar Sekunden dauert, löst Wiederholungsversuche und doppelte Arbeit aus.
- Blocking Stripe an der Firewall oder WAF. Wenn Sie Cloudflare oder eine andere WAF verwenden, lassen Sie den Webhook-Pfad zu und prüfen Sie, dass Bot-Regeln nicht 403 an die Server von Stripe zurückgeben.
Überwachung: So wissen Sie, dass es weiterhin funktioniert
Ein Webhook kann Monate nach dem Start kaputt gehen: Eine bereitgestellte Änderung benennt eine Spalte um, ein Geheimnis wird rotiert, ein Zertifikat läuft ab. Das Stripe Dashboard zeigt Zustellungsversuche und -fehler pro Endpunkt an und Stripe sendet Ihnen eine E-Mail, wenn ein Endpunkt weiterhin ausfällt. Seien Sie Ihrerseits auf drei Dinge aufmerksam: eine wachsende Anzahl received oder failed Ereignisse, einen plötzlichen Rückgang der Ereignisse pro Stunde und eine 4xx- oder 5xx-Rate auf der Webhook-Route. Ein kleiner täglicher Abgleichvorgang, der bezahlte Bestellungen in Ihrer Datenbank mit der Liste erfolgreicher Zahlungen von Stripe vergleicht, erledigt den Rest.
Wie wir helfen können
Wir entwerfen und erstellen Produktionszahlungs- und Webhook-Integrationen mit Signaturprüfungen, Idempotenz, Wiederholungsversuchen und Protokollen, die Ihr Team lesen kann. Wenn Sie bereits über einen Stripe -Flow verfügen und ein zweites Paar Augen benötigen, ist eine Architekturüberprüfung oder -beratung der schnellste Weg, um die Schwachstellen zu finden. Um einen umfassenderen Überblick über das Verschieben kritischer Ereignisse aus Zapier oder Make zu erhalten, lesen Sie „when no-code breaks“, und für einen anderen Anbieter lesen Sie unseren Leitfaden zur WayForPay -Integration in PHP.
Häufige Fragen
Warum schlägt die Überprüfung Stripe Webhook-Signatur fehl?
Die häufigsten Ursachen sind die Verwendung des falschen Signaturgeheimnisses (Test vs. Live, CLI vs. Dashboard-Endpunkt), die Überprüfung eines geparsten oder neu codierten Hauptteils anstelle des rohen Anforderungshauptteils oder ein Proxy, der die Nutzlast ändert. Überprüfen Sie die genauen Rohbytes mit dem eigenen whsec_-Geheimnis des Endpunkts.
Wie verhindere ich, dass ein Stripe -Ereignis zweimal verarbeitet wird?
Speichern Sie jede Ereignis-ID in einer Tabelle mit einem eindeutigen Index und fügen Sie ihn ein, bevor Sie mit der Arbeit beginnen. Wenn das Einfügen mit einem doppelten Schlüssel fehlschlägt, geben Sie 200 zurück und überspringen Sie die Verarbeitung. Sorgen Sie außerdem dafür, dass die Geschäftsaktion selbst wiederholbar ist.
Welchen Antwortcode sollte ein Stripe Webhook zurückgeben?
Geben Sie einen 2xx-Code (200 oder 204) zurück, sobald das Ereignis überprüft und gespeichert wurde. Geben Sie 400 für eine ungültige Signatur und 5xx nur zurück, wenn Sie das Ereignis nicht speichern konnten und möchten, dass Stripe es erneut versucht.
Wie lange versucht Stripe fehlgeschlagene Webhooks erneut auszuführen?
Im Live-Modus wiederholt Stripe bis zu drei Tage lang Wiederholungen mit exponentiellem Backoff. Im Testmodus ist das Wiederholungsfenster viel kürzer. Verlassen Sie sich daher nicht auf Wiederholungsversuche im Testmodus, um das Produktionsverhalten zu beurteilen.
Kann ich Stripe Webhooks ohne öffentliche URL testen?
Ja. Führen Sie „stripe listen --forward-to“ mit der Stripe CLI aus, um Testereignisse an localhost weiterzuleiten, und verwenden Sie den Stripe-Trigger, um Beispielereignisse zu generieren.
Kostenloses Kennenlerngespräch
Unordentlicher Stack oder festgefahrene Integration - Integrono plant den klügsten ersten Schritt.
Sie bekommen eine Senior-Einschätzung zu Budgetfallen, Abhängigkeiten und ob kaufen, kleben oder bauen der richtige Weg ist - bevor die bezahlte Arbeit beginnt. Der CEO bleibt eingebunden.