Zum Inhalt springen

Webhooks

Ein Webhook ist eine HTTP-Anfrage, die Invoise an Ihren Server sendet, wenn sich eine Zahlung ändert. Sie können mit GET-Statusprüfungen beginnen und Webhooks hinzufügen, wenn Sie automatische Updates benötigen.

Terminal-Fenster
curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/webhooks' \
-H 'Authorization: Bearer ivk_...' \
-H 'Idempotency-Key: <saved-unique-key>' \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/invoise","filters":["transfer","invoice","payout"]}'

Verwenden Sie Ihre eigene öffentliche HTTPS-URL. Lokale und private Adressen werden abgelehnt. Speichern Sie das beim Erstellen des Endpunkts zurückgegebene secret: Sie benötigen es, um Anfragen zu verifizieren.

Ein Shop kann bis zu 10 Endpunkte haben. Die URL eines Endpunkts kann geändert werden, verwenden Sie also einen weiter, statt einen weiteren hinzuzufügen. Siehe Limits.

Ereignis Bedeutung
transfer.observed Ein Transfer wurde gesehen; er ist noch nicht bestätigt.
transfer.confirmed Ein eingehender Transfer hat genügend Bestätigungen. Es kann nur eine teilweise Rechnungszahlung sein.
transfer.reverted Ein vorheriges Transfer-Ereignis wurde zurückgenommen.
invoice.closed Die Rechnung wurde on-chain abgeschlossen.
invoice.cancelled Die Zahlungsseite wurde storniert oder die Rechnung ist abgelaufen; dies ist keine Rückerstattung.
payout.sent Die Auszahlung wurde gesendet oder ihr ausgehender Transfer wurde gesehen.
payout.confirmed Eine vollständige Abrechnungsbuchung ist verfügbar.
payout.reverted Eine vorherige Auszahlungsbestätigung wurde zurückgenommen.
payout.failed Ein endgültiger Auszahlungsfehler erfordert ein Eingreifen.

Bei invoice.cancelled gibt data.reason den Grund an: expired, wenn die Rechnungslaufzeit abgelaufen ist, merchant_blocked, wenn das Invoise-Team den Händler gesperrt hat. Eine Stornierung durch den Händler hat keinen reason.

Abonnieren Sie nach Gruppe: transfer, invoice, payout. Eingehende Transfers und ausgehende Auszahlungen sind unterschiedliche Ereignisse. Eine vorübergehende RPC- oder Gas-Verzögerung ist kein payout.failed.

gas.wait und sweep.failed werden als Filter akzeptiert, aber Invoise sendet sie nicht an Ihren Endpunkt. Es sind interne Signale: gas.wait bedeutet, dass eine Auszahlung auf Mittel für die Netzwerkgebühr wartet und von selbst fortfährt; sweep.failed bedeutet, dass ein Versuch, die Mittel zu Ihrem Empfänger zu bewegen, fehlgeschlagen ist. Für keines von beidem ist eine Aktion Ihrerseits nötig. Eine Auszahlung, die nicht abgeschlossen werden kann, erreicht Sie als payout.failed.

In einem Sandbox-Shop können Sie beides über POST /shops/{shop_id}/sandbox/simulate simulieren, um zu prüfen, dass eine verzögerte Auszahlung Ihren Ablauf nicht stört. Sie ändern nur den Zustand der Auszahlung und senden keinen Webhook.

Invoise-Signature hat die Form t=<timestamp>,v1=<hex>. Die Signatur ist HMAC-SHA256 von timestamp + "." + raw_body, unter Verwendung Ihres Webhook-Secrets.

Das folgende Node.js-Beispiel lehnt außerdem Zeitstempel ab, die mehr als fünf Minuten von der Serveruhr abweichen. Halten Sie diese Uhr synchronisiert und wählen Sie Ihre Toleranz bewusst.

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header ?? '');
if (!match) return false;
const [, timestamp, signature] = match;
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac('sha256', secret)
.update(timestamp + '.')
.update(rawBody)
.digest();
return timingSafeEqual(Buffer.from(signature, 'hex'), expected);
}

Übergeben Sie die ursprünglichen Anfrage-Bytes als rawBody. Parsen Sie das JSON nicht und wandeln Sie es nicht wieder in einen String um, bevor Sie die Signatur prüfen. Lehnen Sie ungültige Signaturen ab, bevor Sie ihren Inhalt speichern oder darauf reagieren.

Eine gekürzte Payload sieht so aus:

{
"id": "<event-id>",
"type": "transfer.confirmed",
"created_at": "2026-09-19T10:00:00Z",
"data": {
"issuance_id": "<issuance-id>",
"shop_id": "<shop-id>",
"external_id": "order-123",
"type": "invoice",
"invoice_cancelled": false
}
}
Fall Behandlung
Duplikate Deduplizieren Sie anhand von Invoise-Event-ID, die der id der Envelope entspricht. Speichern Sie die ID und das Geschäfts-Update atomar.
Ein Ereignis annehmen Speichern Sie angenommene Ereignisse dauerhaft, geben Sie umgehend 2xx zurück und verarbeiten Sie anschließend die Warteschlange.
Wiederholungen und Replays Wiederholungen und manuelle Replays verwenden dieselbe Ereignis-ID erneut. Invoise-Attempt-ID ändert sich und ist kein Deduplizierungsschlüssel.
Ereignisreihenfolge Ereignisse können in falscher Reihenfolge eintreffen. Verwenden Sie deren Inhalt und gleichen Sie bei Bedarf mit dem aktuellen Status ab.

Bei Rückabwicklungen verweist chain_event.reference_event_id auf das frühere Chain-Ereignis. Auszahlungs-Payloads enthalten eine stabile payout_id, einen Transaktions-Hash, Beträge und Gebührendetails; unbekannte Beträge sind null. Ihre Liste eingehender Transfer-IDs ist auf 100 begrenzt und markiert eine Kürzung.

Ältere gespeicherte Ereignisse können project_id verwenden; lesen Sie zuerst shop_id und greifen Sie bei Bedarf darauf zurück. Replays behalten die ursprünglichen Bytes.

Listen Sie mit GET /api/v1/shops/{shop_id}/deliveries auf. Wiederholen Sie eine Zustellung mit POST /api/v1/shops/{shop_id}/deliveries/{id}/replay und einem gespeicherten Idempotenzschlüssel. Das Wiederholen erstellt kein neues Geschäftsereignis.