> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fynn.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Lass dein System in Echtzeit über den Lebenszyklus eines Zahlungsplans benachrichtigen. Aufbau des Envelopes, verfügbare Events und Beispiel-Payloads.

Webhooks für Zahlungspläne benachrichtigen dein System automatisch über Ereignisse im Lebenszyklus eines Plans. Sobald ein Ereignis eintritt, sendet Fynn einen `POST`-Request mit strukturierten JSON-Daten an deine hinterlegte URL.

<Info>
  Die Einrichtung, die Zustellung und die Signaturprüfung sind für alle Webhooks gleich. Wie du Webhooks anlegst und absicherst, liest du unter [Webhooks einrichten](/guide/webhooks/introduction). Auf dieser Seite findest du nur, was für Zahlungspläne gilt: die verfügbaren Events und ihre Payloads.
</Info>

## Der Envelope

Jeder Zahlungsplan-Webhook hat denselben Aufbau. Das eigentliche Objekt steckt unter `data`, daneben liegen Metadaten zum Ereignis.

<ResponseField name="event" type="object" required>
  Metadaten zum Ereignis.

  <Expandable title="event">
    <ResponseField name="id" type="string">
      Eindeutige Kennung dieser Zustellung (ULID), identisch mit dem Header `X-Webhook-Id`. Nutze sie, um Wiederholungen idempotent zu verarbeiten.
    </ResponseField>

    <ResponseField name="type" type="string">
      Der Ereignistyp, zum Beispiel `payment_plan.rate_paid`.
    </ResponseField>

    <ResponseField name="version" type="string">
      Schema-Version des Ereignisses, aktuell `v1`.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      Zeitpunkt der Auslösung im ISO-8601-Format.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="data" type="object" required>
  Die Nutzdaten des Ereignisses. Der Schlüssel `paymentPlan` enthält den Plan. Bei `payment_plan.rate_paid` liegt zusätzlich unter `installment` die soeben bezahlte Rate.
</ResponseField>

## HTTP-Header

Jede Zustellung bringt diese Header mit:

| Header                    | Inhalt                                                       |
| ------------------------- | ------------------------------------------------------------ |
| `X-Webhook-Event`         | Der Ereignistyp, zum Beispiel `payment_plan.broken`.         |
| `X-Webhook-Event-Version` | Schema-Version, aktuell `v1`.                                |
| `X-Webhook-Id`            | Eindeutige Kennung der Zustellung, identisch mit `event.id`. |
| `X-Webhook-Signature`     | Signatur des Bodys zur Echtheitsprüfung.                     |
| `X-Tenant-Id`             | Kennung deiner Organisation.                                 |

<Warning>
  Prüfe immer die Signatur aus `X-Webhook-Signature`, bevor du eine Zustellung verarbeitest. So stellst du sicher, dass die Anfrage wirklich von Fynn stammt. Den Ablauf und Beispielcode findest du unter [Webhooks einrichten](/guide/webhooks/introduction#sicherheit-x-webhook-signature).
</Warning>

## Verfügbare Events

| Event                    | Wird ausgelöst, wenn                                          |
| ------------------------ | ------------------------------------------------------------- |
| `payment_plan.created`   | Ein Zahlungsplan für eine offene Rechnung angelegt wird.      |
| `payment_plan.rate_paid` | Eine Rate vollständig gedeckt ist.                            |
| `payment_plan.completed` | Alle Raten bezahlt sind und die Rechnung damit beglichen ist. |
| `payment_plan.broken`    | Ein Einzug ausfällt oder eine Rate überfällig bleibt.         |
| `payment_plan.canceled`  | Ein Plan manuell gekündigt wird.                              |
| `payment_plan.extended`  | Ein Plan mit einem neuen Zeitplan verlängert wird.            |

```mermaid theme={null}
flowchart LR
  A[payment_plan.created] --> B[payment_plan.rate_paid]
  B --> B
  B --> C[payment_plan.completed]
  A --> D[payment_plan.broken]
  D -->|extend| E[payment_plan.extended]
  A --> F[payment_plan.canceled]
```

## Das `paymentPlan`-Objekt

Unter `data.paymentPlan` erhältst du den Plan in seiner Lese-Darstellung, identisch zur Antwort der [Plan-Endpunkte](/v2-payment-plans-api/payment-plans/get-payment-plan). Die wichtigsten Felder:

<ResponseField name="id" type="string">
  Eindeutige Kennung des Plans. Nutze sie, um über [`GET /payment-plans/{id}`](/v2-payment-plans-api/payment-plans/get-payment-plan) den vollständigen Stand nachzuladen.
</ResponseField>

<ResponseField name="invoiceId" type="string">
  Die Rechnung, die aufgeteilt wird.
</ResponseField>

<ResponseField name="status" type="string">
  Der Status des Plans: `active`, `completed`, `broken` oder `canceled`.
</ResponseField>

<ResponseField name="totalAmount" type="integer">
  Summe aller Raten in Cent.
</ResponseField>

<ResponseField name="currencyCode" type="string">
  Währung, zum Beispiel `EUR`.
</ResponseField>

<ResponseField name="brokenReason" type="string | null">
  Grund des Planbruchs, sonst `null`.
</ResponseField>

<ResponseField name="installments" type="array">
  Die Raten des Plans mit `position`, `dueAt`, `amount`, `status` und `paidAt`.
</ResponseField>

## `payment_plan.created`

Feuert, sobald ein Plan angelegt ist. Ab jetzt ruht das Mahnwesen für die Rechnung.

```json theme={null}
{
  "event": {
    "id": "01JZ8F3KQ9X7N2VYB4C6D8E0FG",
    "type": "payment_plan.created",
    "version": "v1",
    "createdAt": "2026-07-05T16:20:00+00:00"
  },
  "data": {
    "paymentPlan": {
      "id": "10000000-0000-0000-00e0-000000000001",
      "invoiceId": "10000000-0000-0000-0003-000000000001",
      "status": "active",
      "totalAmount": 1190,
      "currencyCode": "EUR",
      "paymentMethodId": "10000000-0000-0000-004f-000000000001",
      "createdAt": "2026-07-05T16:20:00+00:00",
      "completedAt": null,
      "brokenAt": null,
      "canceledAt": null,
      "brokenReason": null,
      "installments": [
        { "id": "…", "position": 1, "dueAt": "2026-08-05", "amount": 595, "status": "open", "paidAt": null },
        { "id": "…", "position": 2, "dueAt": "2026-09-05", "amount": 595, "status": "open", "paidAt": null }
      ]
    }
  }
}
```

## `payment_plan.rate_paid`

Feuert, sobald eine Rate vollständig gedeckt ist. Nur bei diesem Event liegt neben `paymentPlan` auch die betroffene Rate unter `installment`.

```json theme={null}
{
  "event": {
    "id": "01JZ8G4M0RY8P3WZC5D7E9F1GH",
    "type": "payment_plan.rate_paid",
    "version": "v1",
    "createdAt": "2026-08-05T04:00:03+00:00"
  },
  "data": {
    "paymentPlan": {
      "id": "10000000-0000-0000-00e0-000000000001",
      "invoiceId": "10000000-0000-0000-0003-000000000001",
      "status": "active",
      "totalAmount": 1190,
      "currencyCode": "EUR",
      "paymentMethodId": "10000000-0000-0000-004f-000000000001",
      "createdAt": "2026-07-05T16:20:00+00:00",
      "completedAt": null,
      "brokenAt": null,
      "canceledAt": null,
      "brokenReason": null,
      "installments": [
        { "id": "…", "position": 1, "dueAt": "2026-08-05", "amount": 595, "status": "paid", "paidAt": "2026-08-05T04:00:02+00:00" },
        { "id": "…", "position": 2, "dueAt": "2026-09-05", "amount": 595, "status": "open", "paidAt": null }
      ]
    },
    "installment": {
      "id": "…",
      "position": 1,
      "dueAt": "2026-08-05",
      "amount": 595,
      "status": "paid",
      "paidAt": "2026-08-05T04:00:02+00:00"
    }
  }
}
```

## `payment_plan.completed`

Feuert, sobald die letzte offene Rate gedeckt ist. Die Rechnung ist damit beglichen.

```json theme={null}
{
  "event": {
    "id": "01JZ9A5N1SZ9Q4XAD6E8F0G2HJ",
    "type": "payment_plan.completed",
    "version": "v1",
    "createdAt": "2026-09-05T04:00:04+00:00"
  },
  "data": {
    "paymentPlan": {
      "id": "10000000-0000-0000-00e0-000000000001",
      "invoiceId": "10000000-0000-0000-0003-000000000001",
      "status": "completed",
      "totalAmount": 1190,
      "currencyCode": "EUR",
      "paymentMethodId": "10000000-0000-0000-004f-000000000001",
      "createdAt": "2026-07-05T16:20:00+00:00",
      "completedAt": "2026-09-05T04:00:03+00:00",
      "brokenAt": null,
      "canceledAt": null,
      "brokenReason": null,
      "installments": [
        { "id": "…", "position": 1, "dueAt": "2026-08-05", "amount": 595, "status": "paid", "paidAt": "2026-08-05T04:00:02+00:00" },
        { "id": "…", "position": 2, "dueAt": "2026-09-05", "amount": 595, "status": "paid", "paidAt": "2026-09-05T04:00:03+00:00" }
      ]
    }
  }
}
```

## `payment_plan.broken`

Feuert, sobald der Plan bricht: ein Lastschrift-Einzug ist fehlgeschlagen, oder eine Rate ist über ihre Frist hinaus unbeglichen geblieben. Der Restbetrag ist ab jetzt sofort fällig, und das Mahnwesen für die Rechnung läuft wieder. `brokenReason` nennt den Auslöser.

```json theme={null}
{
  "event": {
    "id": "01JZ9B6P2T0R5YBE7F9G1H3J4K",
    "type": "payment_plan.broken",
    "version": "v1",
    "createdAt": "2026-08-06T05:00:01+00:00"
  },
  "data": {
    "paymentPlan": {
      "id": "10000000-0000-0000-00e0-000000000001",
      "invoiceId": "10000000-0000-0000-0003-000000000001",
      "status": "broken",
      "totalAmount": 1190,
      "currencyCode": "EUR",
      "paymentMethodId": "10000000-0000-0000-004f-000000000001",
      "createdAt": "2026-07-05T16:20:00+00:00",
      "completedAt": null,
      "brokenAt": "2026-08-06T05:00:00+00:00",
      "canceledAt": null,
      "brokenReason": "installment collection failed",
      "installments": [
        { "id": "…", "position": 1, "dueAt": "2026-08-05", "amount": 595, "status": "failed", "paidAt": null },
        { "id": "…", "position": 2, "dueAt": "2026-09-05", "amount": 595, "status": "open", "paidAt": null }
      ]
    }
  }
}
```

## `payment_plan.canceled`

Feuert, sobald ein Plan manuell gekündigt wird. Der offene Betrag wird wieder normal gemahnt.

```json theme={null}
{
  "event": {
    "id": "01JZ9C7Q3U1S6ZCF8G0H2J4K5L",
    "type": "payment_plan.canceled",
    "version": "v1",
    "createdAt": "2026-08-10T09:15:00+00:00"
  },
  "data": {
    "paymentPlan": {
      "id": "10000000-0000-0000-00e0-000000000001",
      "invoiceId": "10000000-0000-0000-0003-000000000001",
      "status": "canceled",
      "totalAmount": 1190,
      "currencyCode": "EUR",
      "paymentMethodId": "10000000-0000-0000-004f-000000000001",
      "createdAt": "2026-07-05T16:20:00+00:00",
      "completedAt": null,
      "brokenAt": null,
      "canceledAt": "2026-08-10T09:15:00+00:00",
      "brokenReason": null,
      "installments": [
        { "id": "…", "position": 1, "dueAt": "2026-08-05", "amount": 595, "status": "paid", "paidAt": "2026-08-05T04:00:02+00:00" },
        { "id": "…", "position": 2, "dueAt": "2026-09-05", "amount": 595, "status": "open", "paidAt": null }
      ]
    }
  }
}
```

## `payment_plan.extended`

Feuert, sobald ein Plan mit einem neuen Zeitplan verlängert wird. Ein zuvor gebrochener Plan steht danach wieder auf `active`, `brokenAt` und `brokenReason` sind geleert.

```json theme={null}
{
  "event": {
    "id": "01JZ9D8R4V2T7ADG9H1J3K5L6M",
    "type": "payment_plan.extended",
    "version": "v1",
    "createdAt": "2026-08-07T11:00:00+00:00"
  },
  "data": {
    "paymentPlan": {
      "id": "10000000-0000-0000-00e0-000000000001",
      "invoiceId": "10000000-0000-0000-0003-000000000001",
      "status": "active",
      "totalAmount": 1190,
      "currencyCode": "EUR",
      "paymentMethodId": "10000000-0000-0000-004f-000000000001",
      "createdAt": "2026-07-05T16:20:00+00:00",
      "completedAt": null,
      "brokenAt": null,
      "canceledAt": null,
      "brokenReason": null,
      "installments": [
        { "id": "…", "position": 1, "dueAt": "2026-08-05", "amount": 595, "status": "failed", "paidAt": null },
        { "id": "…", "position": 2, "dueAt": "2026-10-05", "amount": 297, "status": "open", "paidAt": null },
        { "id": "…", "position": 3, "dueAt": "2026-11-05", "amount": 298, "status": "open", "paidAt": null }
      ]
    }
  }
}
```

<Tip>
  Antworte mit einem `2xx`-Statuscode, sobald du die Zustellung angenommen hast. Schlägt die Zustellung fehl, wiederholt Fynn sie bis zu dreimal.
</Tip>
