# Webhook-Payloads: Aufbau, Header und Felder je Ereignis

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

https://rabbit-checkout.de/hilfe/referenz/webhook-payloads/ · Aktualisiert: 2026-08-05

---


Diese Seite beschreibt den **Inhalt** eines Webhook-Aufrufs. Einrichtung,
Signaturprüfung und Wiederholungen stehen unter
[Webhooks, Zapier und API](/hilfe/anleitungen/webhooks-und-api/).

## Der Umschlag

Der Körper ist immer ein JSON-Objekt mit derselben äußeren Struktur. Die Daten
zum Vorgang stecken in `payload`:

```json
{
  "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). |

<div class="docs-warn">
Es gibt <strong>keinen Header mit dem Ereignistyp</strong>. Wenn du einen
Endpunkt für mehrere Ereignisse abonniert hast, ist <code>type</code> im Körper
die einzige verlässliche Unterscheidung. Rate nicht über die Form der Daten:
<code>ORDER_CREATED</code> und <code>ORDER_PAYED</code> haben beide ein
<code>order</code>, sie unterscheiden sich nur in den Zusatzfeldern.
</div>

## 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](/hilfe/anleitungen/webhooks-und-api/) |

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](/hilfe/referenz/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.

<div class="docs-note">
<code>payload.order.isTest</code> ist das Feld, mit dem du Testbestellungen
aussortierst, bevor deine Automatisierung etwas ausliefert oder eine Provision
gutschreibt. Ein Testkauf feuert Webhooks ganz normal →
<a href="/hilfe/start/testkauf/">Testkauf machen</a>.
</div>

### 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`.

```json
{
  "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": "kundin@beispiel.de",
      "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.

<div class="docs-note">
Für „jemand hat gekauft, schalte etwas frei" ist
<code>PRODUCT_ACCESS_GRANTED</code> meist das bessere Ereignis als
<code>ORDER_PAYED</code>: Der Payload enthält Produkt und Person schon fertig
aufgeschlüsselt, inklusive <code>externalId</code> und getrenntem Vor- und
Nachnamen.
</div>

## 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.

## 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](/hilfe/start/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

**Wie erkenne ich in einem Webhook, welches Ereignis ausgelöst hat?**

Am Feld `type` im äußeren Umschlag des JSON-Körpers, zum Beispiel &quot;type&quot;: &quot;ORDER_PAYED&quot;. Es gibt keinen Header mit dem Ereignistyp; verlasse dich auf `type`, nicht auf die Reihenfolge oder die Form der Daten.

**Wie ist der Körper eines Webhook-Aufrufs aufgebaut?**

Ein Umschlag mit den Feldern id, storeId, type, payload, createdAt und updatedAt. Die eigentlichen Daten stehen in payload, dessen Aufbau vom Ereignistyp abhängt.

**Welche Header schickt Rabbit Checkout mit?**

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.

**Wo finde ich die E-Mail-Adresse des Käufers im Payload?**

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.

**Wie sehe ich den echten Payload meines eigenen Shops?**

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.


## Passend dazu

- [Anleitung: Webhooks, Zapier und API](https://rabbit-checkout.de/hilfe/anleitungen/webhooks-und-api/)
- [Referenz: Status und Ereignisse](https://rabbit-checkout.de/hilfe/referenz/status-und-ereignisse/)
