Developers
Webhooks
Ein Webhook teilt Ihnen mit, dass eine Bestellung ihren Status geändert hat. Er ersetzt nicht das Lesen der Bestellung: Er ist eine Benachrichtigung, keine Quelle der Wahrheit.
Veröffentlichter Vertrag, noch nicht offen
Webhooks werden heute nicht ausgegeben. Ihre Form, Signatur und Wiederholungsrichtlinie sind festgelegt und hier veröffentlicht, damit Ihr Empfänger im Voraus geschrieben und getestet werden kann.
Ereignisse
Jedes Ereignis trägt denselben Umschlag: eine id, einen Typ, einen Zeitstempel und ein Datenobjekt. Abonnieren Sie die Typen, die Sie verarbeiten, und ignorieren Sie den Rest stillschweigend – neue Typen werden hinzugefügt, und ein Empfänger, der bei einem unbekannten Typ fehlschlägt, zerstört sich selbst.
| Typ | Was es bedeutet |
|---|---|
order.created | Die Bestellung wird erstellt und der Kurs gesperrt. Die Einzahlungsadresse wird zugewiesen. |
order.deposit_detected | Eine eingehende Transaktion wird im Netzwerk gesehen, vor der Bestätigung. Verwenden Sie dieses Ereignis, um den Benutzer zu beruhigen, niemals um etwas auszuliefern. |
order.confirming | Der Bestätigungszähler schreitet voran. Wird bei jedem Schritt ausgegeben, nicht bei jedem Block. |
order.deposit_confirmed | Die vom Netzwerk geforderte Bestätigungsanzahl ist erreicht. |
order.underpaid | Der erhaltene Betrag liegt unter dem erwarteten Betrag außerhalb der Toleranz. Die Bestellung ist nicht verloren: Sie wartet auf eine Wahl zwischen Aufstockung, Fortsetzung mit dem erhaltenen Betrag oder einer Rückerstattung. |
order.overpaid | Der erhaltene Betrag übersteigt den erwarteten Betrag. Der ursprüngliche Betrag bleibt zum gesperrten Kurs; der Überschuss wird separat behandelt. |
order.payout_sent | Die Überweisung hat den Zahlungsweg verlassen. Achtung: Gesendet ist nicht empfangen – die verbleibende Verzögerung hängt von der Bank des Begünstigten ab. |
order.completed | Die Abrechnung ist durch den Zahlungsweg bestätigt. Endzustand. |
order.payout_failed | Der Zahlungsweg hat die Überweisung abgelehnt oder zurückgegeben, mit seinem Grund. Bei einer Bankrückgabe wird kein automatischer erneuter Versuch unternommen: Die Ursache liegt fast immer im Detail. |
order.refunded | Gelder wurden zurückgegeben, nur an die Ursprungsadresse, abzüglich Netzwerkgebühren. |
Die Nutzlast
Ein konstanter Umschlag, unabhängig vom Typ.
{
"id": "evt_01J9ZQF3K8N2M4X7",
"type": "order.payout_sent",
"created": 1788356981,
"data": {
"reference": "K7Q4-M2XB",
"state": "PAYOUT_SENT",
"railId": "sepa_instant",
"netMinor": 91228,
"currency": "EUR",
"sentAt": "2026-09-02T12:21:44Z"
}
}Signatur
Jede Zustellung trägt einen Signatur-Header: einen Zeitstempel und ein HMAC-SHA256, berechnet über diesen Zeitstempel, einen Punkt und den ROHEN Anforderungstext.
Fiatside-Signature: t=<timestamp unix>,v1=<hex hmac-sha256>import { createHmac, timingSafeEqual } from 'node:crypto'
// IMPORTANT : le corps doit etre le corps BRUT, avant tout parsing JSON.
// Re-serialiser l'objet change l'ordre des cles et invalide la signature.
export function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))
const timestamp = Number(parts['t'])
const signature = parts['v1']
if (!timestamp || !signature) return false
// Rejeu : une signature valide capturee hier ne doit pas etre rejouable aujourd'hui.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(signature, 'hex')
// Comparaison a temps constant : un === laisse fuiter la signature octet par octet.
return a.length === b.length && timingSafeEqual(a, b)
}- Signieren Sie den rohen Text, vor jeglichem Parsen. Das erneute Serialisieren des JSON-Objekts ändert die Schlüsselreihenfolge und Leerzeichen: Die Signatur stimmt nicht mehr überein, und Sie werden lange suchen.
- Vergleichen Sie in konstanter Zeit. Ein ===-Vergleich stoppt beim ersten unterschiedlichen Byte, wodurch ein Angreifer die erwartete Signatur Byte für Byte messen kann.
- Lehnen Sie außerhalb des 300-Sekunden-Toleranzfensters ab. Ohne diese Prüfung bleibt eine gültige, gestern erfasste Anfrage heute wiederverwendbar.
- Antworten Sie 200, bevor Sie verarbeiten. Legen Sie das Ereignis in Ihre Warteschlange und behandeln Sie es danach: Lange Verarbeitung führt zu einem Timeout und damit zu einem erneuten Versuch, obwohl Sie das Ereignis empfangen haben.
Wiederholungen und Reihenfolge
Jeder Nicht-2xx-Status oder keine Antwort löst einen erneuten Versuch gemäß dem folgenden Zeitplan aus.
| Versuch | Verzögerung |
|---|---|
| 1 | sofort |
| 2 | 30 s |
| 3 | 2 min |
| 4 | 10 min |
| 5 | 1 h |
| 6 | 6 h |
- Die Ankunftsreihenfolge ist nicht garantiert. Ein Auszahlungsereignis kann vor dem Bestätigungsereignis eintreffen, das ihm logisch vorausgeht: Vertrauen Sie dem Bestellstatus, nicht der Reihenfolge des Empfangs.
- Dasselbe Ereignis kann zweimal eintreffen. Speichern Sie die Ereignis-ID und ignorieren Sie eine bereits verarbeitete ID: Idempotenz auf Ihrer Empfängerseite liegt in Ihrer Verantwortung und ist leicht umzusetzen.
- Vertrauen Sie niemals den Nutzdatenbeträgen, um auf Ihrer Seite etwas gutzuschreiben. Lesen Sie die Bestellung erneut über die API: Die Nutzdaten sagen, dass etwas passiert ist, die API sagt genau, was.
- Nach dem letzten Versuch wird das Ereignis verworfen und bleibt in Ihrem Ereignisprotokoll sichtbar. Sie können es manuell erneut abspielen.