Die E-Mail kam an einem Sonntagmorgen, weitergeleitet von der Betriebsleiterin einer europäischen gemeinnützigen Stiftung, mit einer einzigen Zeile: bitte zur Kenntnis und mit der Bitte um Erledigung. Darunter eine automatische Nachricht von Stripe. Seit mehreren Tagen konnten keine Webhook-Ereignisse an die Spendenseite zugestellt werden, neunzehn Versuche waren gescheitert, und ohne Änderung würde Stripe innerhalb einer Woche keine Benachrichtigungen mehr an diesen Endpunkt senden.
Für eine Organisation, die einmalige und monatliche Spenden über WordPress annimmt, ist das keine Kleinigkeit. Über Webhooks erfährt die Website, dass eine Zahlung tatsächlich erfolgreich war. Ohne sie bleiben Spenden ausstehend, Spender erhalten keine Quittung und monatliche Verlängerungen werden nicht erfasst, obwohl das Geld weiterhin bei Stripe eingeht.
Hier erzählen wir, wie wir den Fehler eingegrenzt haben, warum das erste gefundene Problem zwar echt, aber nicht die Ursache war, und welche kleine Änderung ihn behoben hat, ohne eine einzige Schutzmaßnahme abzuschalten.
Was ein Stripe-Webhook tut und warum ein Ausfall zählt
Wenn jemand spendet, wird der Browser zu Stripe und zurück geleitet. Die Website kann dem Browser beim Zahlungsstatus nicht trauen, deshalb ruft Stripe die Seite zusätzlich direkt auf, von Server zu Server, mit einem Ereignis wie payment_intent.succeeded. Die Seite muss mit einem HTTP-Status zwischen 200 und 299 antworten. Alles andere gilt als Fehler, und Stripe versucht es mit wachsenden Abständen bis zu drei Tage lang erneut, bevor das Ereignis aufgegeben wird.
Das Spenden-Plugin war hier GiveWP, das unter einer Adresse mit der Endung ?give-listener=stripe lauscht. Die Seite ist mehrsprachig, daher gab es zwei aktive Endpunkte, einen unter /de/ und einen unter /fr/. Beide scheiterten, obwohl die E-Mail von Stripe nur einen nannte.

Erster Befund: ein echtes Problem, aber nicht die Ursache
Das WordPress-Dashboard zeigte sofort einen Aktivierungsfehler. Das Add-on Recurring Donations war abgeschaltet worden, weil es eine neuere GiveWP-Version verlangte als installiert war. Das Add-on war aktualisiert worden, das Haupt-Plugin nicht, und WordPress hatte das Add-on stillschweigend deaktiviert. Außerdem lief seine Lizenz ohne Verlängerung aus, es bekam also keine Updates mehr.
Das klang nach der perfekten Erklärung, und reparieren mussten wir es ohnehin. Vollständiges Backup, GiveWP aktualisiert, Add-on reaktiviert. Die öffentliche Seite brach sofort mit einem Fatal Error zusammen. Der Stack-Trace führte zu einem Popup, das den Shortcode eines längst gelöschten Spendenformulars enthielt. Wir haben das Add-on deaktiviert, um die Seite wiederherzustellen, das Popup korrigiert, das Add-on wieder aktiviert und Startseite, Spendenseite und alle Sprachversionen geprüft.
Die Seite lief wieder, und die Option für monatliche Spenden war zurück. Die Webhooks scheiterten trotzdem weiter.
Den echten Fehler lesen statt zu raten
Am schnellsten hört das Raten auf, wenn man im Stripe-Dashboard unter Entwickler, Webhooks eine gescheiterte Zustellung öffnet und zwei Dinge liest: den Statuscode und den Antwort-Body. Hier lautete der Status 403 Forbidden, und der Body war gar keine WordPress-Fehlermeldung. Es war eine HTML-Seite mit dem Titel Just a moment…, die Skripte von challenges.cloudflare.com nachlud.

Diese Seite ist die Browser-Challenge von Cloudflare. Sie verlangt vom Browser einen Nachweis, dass ein Mensch dahintersteht, bevor die Anfrage weitergeleitet wird. Ein Mensch in Chrome besteht sie in einer Sekunde. Die Server von Stripe sind kein Browser, können die Challenge nicht lösen, und so wurde jeder Webhook am Netzwerkrand gestoppt. Die Anfrage erreichte WordPress nie, deshalb stand auch nichts in den Logs der Seite.
Herausfinden, welche Cloudflare-Funktion Stripe blockierte
Mehrere Cloudflare-Funktionen können Traffic herausfordern, und die Lösung hängt davon ab, welche es ist. Unter Sicherheit, Analysen, Ereignisse haben wir nach dem Query-String give-listener gefiltert. Jede passende Anfrage zeigte die Aktion Managed Challenge und den Dienst Security level.

Dieselbe Liste zeigte noch etwas: Auch normale Besucher aus der Schweiz, Frankreich und Deutschland bekamen auf gewöhnlichen Seiten die Challenge. Die Sicherheitsstufe der Zone war sehr hoch eingestellt, vermutlich nach einem früheren Angriff, und niemand hatte bemerkt, dass sie auch den Zahlungsanbieter ausbremste.
Die Lösung: eine schmale Skip-Regel statt einer schwächeren Seite
Die Sicherheitsstufe zu senken hätte funktioniert, hätte aber den Schutz der ganzen Seite verändert, um das Problem einer einzigen URL zu lösen. Sauberer ist eine benutzerdefinierte Regel, die nur die Webhook-Adresse durchlässt und alles andere unverändert lässt.
- Öffnen Sie Sicherheit, Sicherheitsregeln und legen Sie eine neue benutzerdefinierte Regel an.
- Geben Sie ihr einen klaren Namen, zum Beispiel Allow Stripe webhooks.
- Bedingung: URI Query String, contains,
give-listener=stripe. - Wählen Sie die Aktion Skip und haken Sie All remaining custom rules an.
- Öffnen Sie More components to skip und haken Sie Security Level und Browser Integrity Check an.
- Setzen Sie die Regel an erste Stelle und veröffentlichen Sie sie.
(http.request.uri.query contains "give-listener=stripe")

Leicht übersehen wird Schritt fünf. Die erste Fassung der Regel übersprang nur Browser Integrity Check, die API-Vorschau zeigte "products": ["bic"], und die Webhooks wären weiter gescheitert. Laut Ereignisprotokoll kam die Blockade von Security level, also muss genau diese Komponente übersprungen werden. Hinweis für den Free-Plan: Nennt das Protokoll stattdessen Bot Fight Mode, kann eine Skip-Regel das nicht umgehen, und die Einstellung selbst muss ausgeschaltet werden.
Ist das sicher? Die Regel vertraut der Anfrage nicht blind. GiveWP prüft jeden Webhook weiterhin mit dem Signaturschlüssel von Stripe, eine gefälschte Anfrage am Listener wird also von WordPress abgelehnt. Stripe zu authentifizieren war nie die Aufgabe von Cloudflare, sondern die der Signatur.
Die Lösung bestätigen und fehlende Spenden nachholen
Wir haben ein gescheitertes Ereignis aus dem Stripe-Dashboard erneut gesendet, und es kam mit 200 OK zurück. Innerhalb einer Stunde waren auch die automatischen Wiederholungen erfolgreich, und das Dashboard markierte frühere Fehler als Delivery recovered. Am nächsten Tag bestätigte Stripe per E-Mail, dass der Endpunkt wieder funktioniert.

Zwei praktische Hinweise zum Aufräumen. Erstens funktioniert die Schaltfläche zum erneuten Senden nur beim jüngsten Versuch eines Ereignisses. Für Ereignisse, die Stripe bereits aufgegeben hat, öffnen Sie das Ereignis selbst und senden es von dort erneut. Zweitens: Gleichen Sie das Geld ab, nicht nur die Webhooks. Vergleichen Sie jede Stripe-Zahlung seit Beginn des Ausfalls mit den Spendeneinträgen in WordPress. Hier waren alle erfolgreichen Zahlungen bereits erfasst, eine Zahlung war mangels Deckung tatsächlich gescheitert und wurde als fehlgeschlagen markiert, und zwei kleine Testzahlungen aus der Fehlersuche wurden gekennzeichnet, damit niemand sie als Spenden zählt.
Lehren für jede WordPress-Seite, die Zahlungen annimmt
- Zuerst den Antwort-Body lesen. Ein 403 mit HTML Ihres CDN ist kein Anwendungsfehler, und kein Plugin-Debugging wird ihn beheben.
- Sicherheitseinstellungen und Zahlungs-Webhooks müssen zusammenpassen. Nach einem Angriff die Sicherheitsstufe zu erhöhen ist vernünftig; zu vergessen, dass sie auch für Server-zu-Server-Aufrufe gilt, kostet Spenden.
- Den Pfad freigeben, nicht die ganze Welt. Eine schmale Skip-Regel für den Listener hält den Rest der Seite geschützt.
- Abgelaufene Add-on-Lizenzen sind ein schleichendes Risiko. Sie stoppen Updates, und das nächste Core-Update kann das Add-on ohne Vorwarnung deaktivieren.
- Warnungen ernst nehmen. Stripe warnt etwa eine Woche lang, bevor ein Endpunkt deaktiviert wird. Sorgen Sie dafür, dass diese E-Mails jemanden erreichen, der handeln kann.
Über einen ähnlichen Grenzfall, bei dem Cache und WordPress sich bei der Zeit uneinig waren, haben wir in einem sporadischen 403-Fehler durch die Cache-TTL geschrieben, und über die Routineprüfungen, die solche Probleme früh erkennen, in was Website-Wartung wirklich umfasst.
Häufige Fragen
Warum liefern meine Stripe-Webhooks 403, obwohl die Seite in meinem Browser funktioniert?
Weil Ihr Browser die Cloudflare-Challenge besteht und die Server von Stripe nicht. Erwähnt der Antwort-Body Just a moment oder challenges.cloudflare.com, wird die Anfrage bei Cloudflare gestoppt, bevor sie WordPress erreicht.
Sollte ich stattdessen die IP-Adressen von Stripe freigeben?
Das geht mit der von Stripe veröffentlichten IP-Liste, doch die ändert sich und muss gepflegt werden. Eine Skip-Regel auf dem Webhook-Pfad ist einfacher und, weil das Plugin die Signatur prüft, in der Praxis genauso sicher.
Gehen Spenden verloren, solange die Webhooks scheitern?
Stripe zieht das Geld in der Regel ein, aber die Website erfasst es nicht. Spenden bleiben ausstehend, Quittungen werden nicht versandt und wiederkehrende Verlängerungen fehlen, bis die Ereignisse zugestellt oder die Einträge von Hand korrigiert sind.
Wie lange versucht Stripe es erneut?
Im Live-Modus bis zu drei Tage mit wachsenden Abständen. Scheitert ein Endpunkt weiter, schickt Stripe eine Warnung per E-Mail und deaktiviert ihn schließlich.
Sie betreiben Spenden, einen Shop oder Mitgliedschaften mit WordPress und bekommen Zahlungswarnungen, die Sie nicht einordnen können? Genau solche Analysen übernehmen wir im Rahmen unserer WordPress-Entwicklung und Wartung. Sprechen wir darüber, und wir sagen Ihnen klar, was nicht stimmt und was die Behebung kostet.