Webhooks
Setzen Sie callbackUrl für eine Zahlung, und Stablezact sendet POST-Anfragen
an diese URL, während die Zahlung fortschreitet. Das ist der zuverlässige Weg zu
erfahren, dass eine Zahlung abgewickelt ist, weil er nicht davon abhängt, dass
der Kunde einen Browser-Tab offen lässt.
config={{ amount: 49.99, currency: 'USDC', callbackUrl: 'https://yourstore.com/webhooks/stablezact', orderId: 'ORD-1234', metadata: { basket: 'b-99' },}}Sowohl orderId als auch metadata werden bei jedem Ereignis zurückgegeben.
So ordnen Sie ein Ereignis einer Bestellung zu, ohne eine eigene Zuordnung zu
pflegen.
Ereignisse
Abschnitt betitelt „Ereignisse“Lesen Sie type. Es ist dasselbe kleine Vokabular, egal wie der Kunde bezahlt
hat.
type |
Bedeutung | Ausführen |
|---|---|---|
payment.pending |
Geld wurde erkannt, die Zahlung ist noch nicht final | nein |
payment.succeeded |
der Kunde hat bezahlt | ja |
payment.underpaid |
weniger als der Rechnungsbetrag ist angekommen | nein, Kunden darauf hinweisen |
payment.failed |
abgelaufen, storniert oder fehlgeschlagen | nein |
payment.refunded |
das Geld ging an den Kunden zurück | Bestellung rückgängig machen |
settlement.completed |
Ihre Auszahlung hat Ihre Wallet erreicht | bereits ausgeführt |
settlement.delayed |
Ihre Auszahlung dauert länger als üblich, wird automatisch erneut versucht | nichts zu tun |
settlement.failed |
Ihre Auszahlung konnte nicht abgeschlossen werden, wir kontaktieren Sie | nichts zu tun |
webhook.test |
eine Testzustellung | nein |
payment.succeeded ist das einzige Ereignis, das „ausführen“ bedeutet. Alle
anderen sind da, falls Sie sie brauchen.
Die payment-Ereignisse beantworten die Frage „Soll ich diese Bestellung
versenden?“. Die settlement-Ereignisse beantworten „Wo ist mein Geld?“, und
sie ändern nie etwas daran, ob Sie versenden. Viele Integrationen ignorieren sie
und gleichen stattdessen über ihre Wallet ab.
type wird außerdem als Header X-Webhook-Type gesendet, falls Sie lieber
anhand eines Headers routen, als den Body zu parsen.
Das ältere Feld event
Abschnitt betitelt „Das ältere Feld event“Jede Payload enthält außerdem ein Feld event, das älter ist als type. Es
verwendet zwei Namenskonventionen, gibt demselben Ergebnis je nach
Zahlungsweise des Kunden zwei verschiedene Namen und benennt unsere internen
Auszahlungsschritte. Es funktioniert weiterhin und wird auch weiterhin
funktionieren, bestehende Integrationen müssen also nicht geändert werden.
Bauen Sie neue Integrationen auf type auf. Wenn Sie eine ältere lesen, so
entsprechen sich die beiden:
event |
type |
|---|---|
payment_success, deposit.confirmed |
payment.succeeded |
payment_failure, deposit.failed, bridge.failed |
payment.failed |
deposit.detected, bridge.filled |
payment.pending |
deposit.swept, settlement.completed |
settlement.completed |
settlement.delayed |
settlement.delayed |
payment.underpaid |
payment.underpaid |
payment.refunded |
payment.refunded |
webhook_test |
webhook.test |
Die Zahlung ist in jeder Payload paymentId. Manche älteren Ereignisse nennen
sie außerdem transactionId; beide sind vorhanden und identisch. Die
Auszahlungstransaktion ist settlementTxHash, bei älteren Ereignissen auch als
sweepTxHash vorhanden.
Wenn Sie nicht sicher sind, welches Ereignis Sie vor sich haben, rufen Sie die
Zahlung ab und lesen Sie outcome. Es ist dieselbe Antwort, und sie hängt nicht
davon ab, welche Ereignisse Sie erreicht haben.
Prüfen, ob die Anfrage von uns stammt
Abschnitt betitelt „Prüfen, ob die Anfrage von uns stammt“Jede Zustellung enthält zwei Header:
X-Webhook-Signature: sha256=<hex digest>X-Webhook-Timestamp: <milliseconds since epoch>Verifizieren Sie, bevor Sie auf den Body reagieren. Ein Endpunkt, der Bestellungen auf einen unverifizierten POST hin ausführt, führt Bestellungen für jeden aus, der die URL findet.
Verwenden Sie nach Möglichkeit den SDK-Helper
Abschnitt betitelt „Verwenden Sie nach Möglichkeit den SDK-Helper“Das SDK liefert die Verifizierung mit, sodass Sie sie nicht neu implementieren müssen:
import { verifyWebhookSignature } from 'stablezact-pay';
app.post('/webhooks/stablezact', express.json(), (req, res) => { const result = verifyWebhookSignature( req.body, req.headers['x-webhook-signature'], req.headers['x-webhook-timestamp'], process.env.STABLEZACT_WEBHOOK_SECRET );
if (!result.valid) return res.status(400).json({ error: result.error });
res.sendStatus(200); handleEvent(req.body);});Es gibt außerdem coinleyWebhookMiddleware, falls Sie die Verifizierung lieber
als Middleware einbinden möchten.
Selbst verifizieren
Abschnitt betitelt „Selbst verifizieren“import crypto from 'node:crypto';
function verify(payload, signatureHeader, timestampHeader, secret) { const ts = parseInt(timestampHeader, 10);
// Reject anything older than five minutes, so a captured delivery cannot be // replayed later. if (Date.now() - ts > 5 * 60 * 1000) return false;
const withTimestamp = { ...payload, timestamp: ts }; const serialized = JSON.stringify(withTimestamp, Object.keys(withTimestamp).sort());
const expected = crypto.createHmac('sha256', secret).update(serialized).digest('hex'); const received = signatureHeader.replace(/^sha256=/, '');
const a = Buffer.from(received, 'hex'); const b = Buffer.from(expected, 'hex'); return a.length === b.length && crypto.timingSafeEqual(a, b);}Drei Details, auf die es ankommt:
Der timestamp ist Teil des signierten Materials und muss daher vor dem
Serialisieren als Zahl zur Payload hinzugefügt werden.
Die Schlüssel werden sortiert. JSON.stringify(value, Object.keys(value).sort())
ist genau der Aufruf, den der Server ausführt.
Ihr Webhook-Secret muss mindestens 32 Zeichen lang sein. Kürzere Secrets werden vom Verifizierer abgelehnt, statt die Signatur stillschweigend zu schwächen.
Zustellungen verarbeiten
Abschnitt betitelt „Zustellungen verarbeiten“Antworten Sie schnell mit 200 und erledigen Sie die Arbeit danach. Ein
langsamer Endpunkt sieht aus wie ein fehlgeschlagener.
Rechnen Sie damit, dass dasselbe Ereignis zweimal ankommt. Wiederholungen und
Netzwerk-Timeouts bedeuten eine Zustellung mindestens einmal (at-least-once).
Daher lohnen sich die paar Zeilen, um anhand von paymentId plus event zu
deduplizieren und Wiederholungen zu ignorieren.
