Webhook-Payloads
Was genau im Körper eines Webhook-Aufrufs steht: der äußere Umschlag, die Header, und pro Ereignis die Felder darin.
Diese Seite beschreibt den Inhalt eines Webhook-Aufrufs. Einrichtung, Signaturprüfung und Wiederholungen stehen unter Webhooks, Zapier und API.
Der Umschlag
Der Körper ist immer ein JSON-Objekt mit derselben äußeren Struktur. Die Daten zum Vorgang stecken in payload:
{
"id": 4711,
"storeId": 1,
"type": "ORDER_PAYED",
"payload": { "order": { "…": "…" }, "invoice": { "…": "…" }, "payment": { "…": "…" } },
"createdAt": "2026-08-05T09:12:44.318Z",
"updatedAt": "2026-08-05T09:12:44.318Z"
}
| Feld | Bedeutung |
|---|---|
id | Die ID des Ereignisses. Eindeutig, aufsteigend, taugt als Schlüssel gegen Doppelverarbeitung. |
storeId | Der Shop, in dem der Vorgang passiert ist. |
type | Der Ereignistyp, siehe unten. Das ist das Feld, an dem du unterscheidest. |
payload | Die Daten. Aufbau je Typ, siehe unten. |
createdAt, updatedAt | Zeitstempel des Ereignisses (ISO 8601, UTC). |
type im Körper die einzige verlässliche Unterscheidung. Rate nicht über die Form der Daten: ORDER_CREATED und ORDER_PAYED haben beide ein order, sie unterscheiden sich nur in den Zusatzfeldern.Die Header
| Header | Immer? | Inhalt |
|---|---|---|
content-type | ja | application/json |
x-webhook-id | ja | Die ID des Webhooks, der diesen Aufruf ausgelöst hat. Nützlich, wenn mehrere Webhooks auf denselben Endpunkt zeigen. |
x-signature | nur mit Secret | HMAC-SHA256 über den Rohkörper, hexadezimal → Signatur prüfen |
Der Aufruf ist ein POST und wird nach 10 Sekunden abgebrochen. Antworte also schnell und arbeite die Aufgabe danach ab, statt den Aufruf offen zu halten.
Die Payloads je Ereignis
Bestellungen
| Ereignis | Schlüssel in payload |
|---|---|
ORDER_CREATED | order |
ORDER_PAYED | order, invoice, payment |
ORDER_FAILED | order |
ORDER_REFUNDED | order, refund, bei einem Widerruf zusätzlich withdrawal |
order ist der vollständige Bestelldatensatz. Die Felder, die man in der Praxis braucht:
| Pfad | Inhalt |
|---|---|
payload.order.id | Interne Bestell-ID |
payload.order.orderNo | Die Bestellnummer, wie sie der Kunde sieht (z. B. ORD-0042) |
payload.order.status | Bestellstatus → Status und Ereignisse |
payload.order.isTest | true bei einer Testbestellung |
payload.order.customerEmail | E-Mail des Käufers |
payload.order.customerName | Name, wie eingegeben |
payload.order.subtotal, .discount, .taxes, .total | Die Beträge der Bestellung |
payload.order.currency | Währung, z. B. EUR |
payload.order.couponCode | Eingelöster Gutscheincode, sonst null |
payload.order.reverseChargeApplied | true, wenn Reverse Charge gegriffen hat |
payload.order.items[] | Positionen mit productName, productId, quantity, unitPrice, taxRate, tax, productExternalId |
payload.order.billingAddress | Rechnungsadresse, shippingAddress analog |
Die Felder items, billingAddress und shippingAddress sind bei allen Bestell-Ereignissen mitgeliefert; du musst sie nicht nachladen.
Bei ORDER_PAYED und ORDER_FAILED kommen zusätzlich payload.order.customerFirstName und payload.order.customerLastName dazu, der aufgeteilte Name. Bei ORDER_CREATED sind sie nicht enthalten.
payload.order.isTest ist das Feld, mit dem du Testbestellungen aussortierst, bevor deine Automatisierung etwas ausliefert oder eine Provision gutschreibt. Ein Testkauf feuert Webhooks ganz normal → Testkauf machen.Abonnements
| Ereignis | Schlüssel in payload |
|---|---|
SUBSCRIPTION_CREATED | subscription |
SUBSCRIPTION_ACTIVATED | subscription |
SUBSCRIPTION_PAUSED | subscription |
SUBSCRIPTION_CANCELED | subscription |
SUBSCRIPTION_EXPIRED | subscription |
subscription ist der Abo-Datensatz mit id, status, storeId, customerId, der zugrunde liegenden Preisoption und den Abrechnungsdaten.
Jede einzelne Abbuchung eines Abos erzeugt zusätzlich eine eigene Bestellung mit eigener Rechnung, also ein eigenes ORDER_PAYED. Wenn du auf „es wurde wieder abgebucht" reagieren willst, ist ORDER_PAYED das Ereignis, nicht ein Abo-Ereignis.
Produktzugang
PRODUCT_ACCESS_GRANTED und PRODUCT_ACCESS_REVOKED haben keinen Zwischenschlüssel: die Felder liegen direkt in payload.
{
"type": "PRODUCT_ACCESS_GRANTED",
"payload": {
"id": 82,
"customerId": 17,
"productId": 3,
"storeId": 1,
"createdAt": "2026-08-05T09:12:44.318Z",
"updatedAt": "2026-08-05T09:12:44.318Z",
"customer": {
"id": 17,
"email": "[email protected]",
"name": "Anna Müller",
"firstName": "Anna",
"lastName": "Müller",
"country": "DE"
},
"product": { "id": 3, "name": "Onboarding-Workshop", "externalId": null },
"store": { "id": 1, "name": "Beispiel-Shop" },
"source": { "type": "order", "status": "PAID" }
}
}
| Pfad | Inhalt |
|---|---|
payload.customer.email | E-Mail der Person, die Zugang bekommt |
payload.customer.firstName, .lastName | Bereits aufgeteilt |
payload.customer.country | Land aus der Rechnungsadresse des auslösenden Vorgangs. Kann fehlen. |
payload.product.externalId | Deine eigene Artikelnummer, falls am Produkt hinterlegt. Sonst null. |
payload.source.type | order oder subscription, je nachdem was den Zugang ausgelöst hat |
payload.source.status | Der Status dieses Vorgangs |
source gibt es nur bei PRODUCT_ACCESS_GRANTED. Bei PRODUCT_ACCESS_REVOKED fehlt es.
PRODUCT_ACCESS_GRANTED meist das bessere Ereignis als ORDER_PAYED: Der Payload enthält Produkt und Person schon fertig aufgeschlüsselt, inklusive externalId und getrenntem Vor- und Nachnamen.Sauber verarbeiten
Drei Regeln, die Ärger ersparen:
- Auf
typeverzweigen, nicht auf die Form. Und in eigenen Programmen unbekanntetype-Werte ignorieren statt Fehler werfen: Es können Ereignisse dazukommen. - Den Käufer am festen Pfad lesen, nicht durch den Körper suchen. Ein Payload enthält mehrere E-Mail-Adressen und Namen (Kunde, Shop, Rechnungsadresse). „Nimm das erste, was wie eine E-Mail aussieht" trifft im Zweifel die falsche.
idgegen Doppelverarbeitung merken. Ein Aufruf wird bis zu dreimal wiederholt, wenn dein Endpunkt nicht mit Erfolg antwortet, auch dann, wenn du die Aufgabe eigentlich schon erledigt hattest.
Den echten Payload deines Shops ansehen
Verlass dich nicht auf diese Seite allein, wenn du etwas Verlässliches baust, sieh in deine eigenen Daten:
- Webhook einrichten und die gewünschten Ereignisse abonnieren.
- Einen Testkauf machen.
- Einstellungen → Ereignisse öffnen. Jeder Eintrag zeigt den gesendeten Inhalt, die Zieladresse, den Antwortstatus und den Antworttext, und lässt sich von dort erneut auslösen.
Das ist auch der schnellste Weg, eine Automatisierung zu debuggen, ohne auf einen echten Kunden zu warten.
Häufige Fragen
Am Feld `type` im äußeren Umschlag des JSON-Körpers, zum Beispiel "type": "ORDER_PAYED". Es gibt keinen Header mit dem Ereignistyp; verlasse dich auf `type`, nicht auf die Reihenfolge oder die Form der Daten.
Ein Umschlag mit den Feldern id, storeId, type, payload, createdAt und updatedAt. Die eigentlichen Daten stehen in payload, dessen Aufbau vom Ereignistyp abhängt.
content-type: application/json, x-webhook-id mit der ID des auslösenden Webhooks und, wenn ein Secret hinterlegt ist, x-signature mit dem HMAC-SHA256 über den Rohkörper.
Bei Bestell-Ereignissen unter payload.order.customerEmail, bei Produktzugängen unter payload.customer.email. Suche die Adresse nicht irgendwo im Körper, sondern lies sie an dem Pfad, der zum Ereignistyp gehört.
Einstellungen → Ereignisse. Jeder Eintrag zeigt den gesendeten Inhalt, die Zieladresse, den Antwortstatus und den Antworttext. Ein Testkauf erzeugt echte Ereignisse, mit denen du das prüfen kannst.
Frage nicht beantwortet? Schreib uns — deutscher Support, echte Menschen.