Stripe-Zahlungen mit Kassa-Sound aufs Handy
Stripes Dashboard ist der Ort zum Nachschauen. Nicht der Ort, an dem du es erfährst. Ein Webhook aufs Handy macht aus jeder Zahlung, jeder gescheiterten Abbuchung und jedem Chargeback einen Ton, den du erkennst, ohne etwas zu lesen.
Einen Stripe-Webhook einrichten
Stripe schickt ein JSON-Payload über HTTPS an einen Endpunkt, den du registrierst – jedes Mal, wenn in deinem Konto ein abonniertes Ereignis eintritt. Der Endpunkt muss eine öffentlich erreichbare HTTPS-URL sein, und du kannst bis zu 16 Ereignisziele registrieren.
- Leg in der Webhooky-App einen Endpunkt an. Nenn ihn Stripe, setz die Nachricht, wähl den Sound
cash_registerund kopier die URL. - Öffne in Stripe Workbench → Webhooks und füge einen Endpunkt mit dieser URL hinzu. (Workbench löst das ältere Entwickler-Dashboard ab; dort einen Endpunkt anzulegen funktioniert weiterhin, Stripe empfiehlt aber Workbench.)
- Wähl die gewünschten Events aus und speichere.
Stripes Payloads sind große, verschachtelte Event-Objekte. Webhooky parst sie nicht – weil der Body kein Feld title oder message hat, greift der Fallback auf Titel, Text und Sound vom Endpunkt. Du bekommst sofort einen klanglich eindeutigen Alarm, ohne Server dazwischen.
Die Events, die einen eigenen Sound verdienen
Hier schlägt ein Endpunkt pro Event das Dashboard. Ein Endpunkt je Ereignis, ein Sound je Endpunkt:
checkout.session.completed– eine Checkout-Session wurde bezahlt. Die Kassa.payment_intent.succeeded– eine Zahlung ging durch, bei eigenen Flows.payment_intent.payment_failed– eine Zahlung ist gescheitert. Ein Buzzer.charge.refunded– du hast eine Abbuchung erstattet.charge.dispute.created– ein Chargeback. Der darf unangenehm klingen.invoice.payment_failed– eine Abo-Verlängerung ist gescheitert. Fang es ab, bevor der Kunde abspringt.customer.subscription.deleted– jemand hat gekündigt.
Die beiden wertvollsten sind hier meist nicht die erfreulichen. Bei invoice.payment_failed und charge.dispute.created ändert eine Reaktion innerhalb einer Stunde statt einer Woche tatsächlich den Ausgang – und genau die gehen in einer E-Mail-Liste unter.
Was Stripe macht, wenn dein Endpunkt ausfällt
Stripe wiederholt im Live-Modus mit exponentiellem Backoff bis zu drei Tage lang. Wann der nächste Versuch ansteht, siehst du im Reiter Ereignisübermittlungen deines Ereignisziels. In einer Sandbox ist es sanfter: drei Versuche innerhalb weniger Stunden. Ist dein Ziel beim Wiederholungsversuch deaktiviert oder gelöscht, wird dieses Ereignis nicht erneut versucht.
Webhooky antwortet immer mit 200, auch wenn du die 100 Benachrichtigungen des Gratis-Tarifs aufgebraucht hast – du bekommst stattdessen eine separate Meldung über das Limit. Das zählt, weil eine lange Fehlerserie genau der Weg ist, auf dem ein Zahlungs-Alarm stillschweigend aufhört zu funktionieren.
Wenn du Betrag und Kunde willst
Damit „129,00 € · [email protected]“ drinsteht statt „eine Zahlung ist eingegangen“, nimmst du den Webhook im eigenen Handler entgegen, prüfst ihn und schickst eine formatierte Nachricht weiter:
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function onStripeWebhook(req, res) {
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody, // der ROHE Body, kein geparstes JSON
req.get("Stripe-Signature"),
process.env.STRIPE_WEBHOOK_SECRET, // beginnt mit whsec_
);
} catch {
return res.sendStatus(400);
}
res.sendStatus(200);
if (event.type !== "checkout.session.completed") return;
const s = event.data.object;
await fetch(process.env.WEBHOOKY_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "💳 Stripe-Zahlung",
message: `${(s.amount_total / 100).toFixed(2)} ${s.currency.toUpperCase()} · ${s.customer_details?.email ?? ""}`,
sound: "cash_register",
}),
});
}
Der mit Abstand häufigste Fehler hier: Stripe braucht den rohen Request-Body für die Signaturprüfung. Parst dein Framework das JSON, bevor du drankommst, ändern sich die Bytes und die Prüfung scheitert jedes Mal. In Express heißt das express.raw({type: "application/json"}) speziell für diese Route – nicht das globale express.json().
Das Signing-Secret jedes Endpunkts beginnt mit whsec_ und steht auf der Webhook-Einstellungsseite in Workbench. Wechsle es regelmäßig, oder sofort, wenn du einen Abfluss vermutest.
Testen, bevor du dich darauf verlässt
Mit der Stripe-CLI feuerst du ein echtes Event auf deinen Endpunkt, ohne eine Zahlung auszulösen:
stripe trigger payment_intent.succeeded
Zeigt der Endpunkt direkt auf Webhooky, sollte dein Handy innerhalb von ein, zwei Sekunden klingeln. Hast du einen Handler gebaut, kannst du mit stripe listen --forward-to localhost:3000/webhook gegen echte Events entwickeln, ohne zu deployen.
Warum nicht einfach Stripes E-Mail-Belege?
Die meisten nutzen beides. Die E-Mail ist der Beleg, der Push mit eigenem Sound ist das, was dich an einem Sonntag erreicht. Das eigentliche Argument sind die unerfreulichen Ereignisse – niemand schaut schnell genug ins Postfach für einen Chargeback, und invoice.payment_failed bei einem Abo ist bares Geld wert, wenn du es am selben Tag siehst. Jeder Endpunkt führt seine eigene Historie, und es gibt kein Konto anzulegen: App installieren, Endpunkt erstellen, URL in Workbench einfügen.
Häufige Fragen
Wie bekomme ich eine Benachrichtigung bei jeder Stripe-Zahlung?
Leg in Workbench → Webhooks einen Endpunkt an, abonniere checkout.session.completed (oder payment_intent.succeeded) und richte ihn auf eine URL, die dich benachrichtigt. Ein Webhooky-Endpunkt macht aus diesem POST einen Push mit eigenem Sound, ganz ohne Server.
Kann ich einen Sound bekommen, wenn eine Stripe-Zahlung eingeht?
Von Stripe selbst nicht, über einen Webhook schon. Richte das Zahlungs-Event auf einen Benachrichtigungs-Endpunkt mit Sound-Unterstützung und weise eine Registrierkasse zu. Webhooky hat 40 Sounds, pro Endpunkt einstellbar oder pro Nachricht überschreibbar.
Wie lange wiederholt Stripe einen fehlgeschlagenen Webhook?
Im Live-Modus mit exponentiellem Backoff bis zu drei Tage. In einer Sandbox sind es drei Versuche innerhalb weniger Stunden. Der nächste geplante Versuch ist im Reiter Ereignisübermittlungen sichtbar.
Warum scheitert meine Stripe-Signaturprüfung ständig?
Fast immer, weil die Signatur über geparstes und neu serialisiertes JSON berechnet wird statt über den rohen Request-Body. Stripe verlangt den Body exakt so, wie er gesendet wurde; jede Umformatierung bricht die Prüfung.