Verkaufen
Abrechnen
Ausliefern & wachsen
Vergleich
Ressourcen
Preise Jetzt starten – 100 € Rabatt Login

Webhook-Payloads

Was genau im Körper eines Webhook-Aufrufs steht: der äußere Umschlag, die Header, und pro Ereignis die Felder darin.

ReferenzAktualisiert: 17. September 2026

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"
}
FeldBedeutung
idDie ID des Ereignisses. Eindeutig, aufsteigend, taugt als Schlüssel gegen Doppelverarbeitung.
storeIdDer Shop, in dem der Vorgang passiert ist.
typeDer Ereignistyp, siehe unten. Das ist das Feld, an dem du unterscheidest.
payloadDie Daten. Aufbau je Typ, siehe unten.
createdAt, updatedAtZeitstempel des Ereignisses (ISO 8601, UTC).
Es gibt keinen Header mit dem Ereignistyp. Wenn du einen Endpunkt für mehrere Ereignisse abonniert hast, ist 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#

HeaderImmer?Inhalt
content-typejaapplication/json
x-webhook-idjaDie ID des Webhooks, der diesen Aufruf ausgelöst hat. Nützlich, wenn mehrere Webhooks auf denselben Endpunkt zeigen.
x-signaturejaHMAC-SHA256 über den Rohkörper, hexadezimal. Das Secret dafür wird beim Anlegen des Webhooks automatisch erzeugt → 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#

EreignisSchlüssel in payload
ORDER_CREATEDorder
ORDER_PAYEDorder, invoice, payment — ohne Rechnungsstellung entfällt invoice, payment bleibt
ORDER_FAILEDorder
ORDER_REFUNDEDorder, refund, bei einem Widerruf zusätzlich withdrawal

order ist der vollständige Bestelldatensatz. Die Felder, die man in der Praxis braucht:

PfadInhalt
payload.order.idInterne Bestell-ID
payload.order.orderNoDie Bestellnummer, wie sie der Kunde sieht (z. B. ORD-0042)
payload.order.statusBestellstatus → Status und Ereignisse
payload.order.isTesttrue bei einer Testbestellung
payload.order.customerEmailE-Mail des Käufers
payload.order.customerNameName, wie eingegeben
payload.order.subtotal, .discount, .taxes, .totalDie Beträge der Bestellung
payload.order.currencyWährung, z. B. EUR
payload.order.couponCodeEingelöster Gutscheincode, sonst null
payload.order.reverseChargeAppliedtrue, wenn Reverse Charge gegriffen hat
payload.order.smallBusinessAppliedtrue, wenn der Shop beim Verkauf auf Kleinunternehmer § 19 stand
payload.order.notTaxableAppliedtrue, wenn der Shop beim Verkauf auf Nicht steuerbar stand. Beide Felder halten den Stand zum Zeitpunkt des Verkaufs fest, nicht den heutigen → Mehrwertsteuer einstellen
payload.order.items[]Positionen mit productName, productId, quantity, unitPrice, taxRate, tax, productExternalId
payload.order.billingAddressRechnungsadresse, shippingAddress analog

| payload.order.formFields[] | Antworten auf deine eigenen Formularfelder. Ein Array, je Eintrag id, label, type (checkbox oder textbox), required und value — kein Objekt mit dem Feldnamen als Schlüssel. | | payload.order.consents[] | Die Einwilligungen, denen der Käufer zugestimmt hat: type, text, linkText, linkUrl, mode, required, accepted. Die AGB-Zustimmung steht hier als type: "TERMS_AND_CONDITIONS". | | payload.order.marketingConsent | Die Cookie-Entscheidung fürs Marketing-Tracking: true, false oder null, wenn keine getroffen wurde. Nicht zu verwechseln mit consents[], den Häkchen im Formular. | | payload.order.marketingConsentAt | Zeitpunkt dieser Entscheidung | | payload.order.marketingConsentVersion | Version des angezeigten Bannertextes. 0 bei einer weitergeleiteten Entscheidung, weil dem Käufer kein Rabbit-Text angezeigt wurde. | | payload.order.marketingConsentSource | "BANNER" (auf dem Rabbit-Banner entschieden), "FORWARDED" (von deiner eingebetteten Seite übergeben → Checkout einbetten) oder null | | payload.order.acceptedTerms | Altfeld, nicht auswerten. Steht bei allen Bestellungen aus dem Checkout auf false, seit die AGB-Zustimmung im August 2026 in consents umgezogen ist. Es hält nur noch den Stand von davor. Ob AGB zugestimmt wurde, steht in consents. |

Die Felder items, billingAddress und shippingAddress sind bei allen Bestell-Ereignissen mitgeliefert; du musst sie nicht nachladen.

formFields ist ein Array. Wer es als Objekt zuordnet („Feldname → Wert"), bekommt beim ersten echten Aufruf nichts. Lies das Label aus label und den Wert aus value.

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#

EreignisSchlüssel in payload
SUBSCRIPTION_CREATEDsubscription
SUBSCRIPTION_ACTIVATEDsubscription
SUBSCRIPTION_SCHEDULEDsubscription
SUBSCRIPTION_PAUSEDsubscription
SUBSCRIPTION_CANCELEDsubscription
SUBSCRIPTION_EXPIREDsubscription

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" }
  }
}
PfadInhalt
payload.customer.emailE-Mail der Person, die Zugang bekommt
payload.customer.firstName, .lastNameBereits aufgeteilt
payload.customer.countryLand aus der Rechnungsadresse des auslösenden Vorgangs. Kann fehlen.
payload.product.externalIdDeine eigene Artikelnummer, falls am Produkt hinterlegt. Sonst null.
payload.source.typeorder oder subscription, je nachdem was den Zugang ausgelöst hat
payload.source.statusDer Status dieses Vorgangs

source gibt es nur bei PRODUCT_ACCESS_GRANTED. Bei PRODUCT_ACCESS_REVOKED fehlt es.

Für „jemand hat gekauft, schalte etwas frei" ist 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:

  1. Auf type verzweigen, nicht auf die Form. Und in eigenen Programmen unbekannte type-Werte ignorieren statt Fehler werfen: Es können Ereignisse dazukommen.
  2. 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.
  3. id gegen 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.

Ein Beispiel anfordern, ohne zu verkaufen#

Du musst für den ersten Payload nicht auf einen echten Kauf warten: Auf der Webhook-Seite verschickt Simulieren ein Beispiel an deine Adresse, in derselben Struktur wie ein echtes Ereignis → Webhooks, Zapier und API.

Den echten Payload deines Shops ansehen#

Verlass dich nicht auf diese Seite allein, wenn du etwas Verlässliches baust, sieh in deine eigenen Daten:

  1. Webhook einrichten und die gewünschten Ereignisse abonnieren.
  2. Einen Testkauf machen.
  3. 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 x-signature mit dem HMAC-SHA256 über den Rohkörper. Das Secret für die Signatur wird beim Anlegen des Webhooks automatisch erzeugt.

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.