Source: https://docs.debitura.com/clients/integration-guides/webhooks

# Webhooks

Receive real-time notifications when case events occur instead of polling the API.

This guide assumes you've completed the [Quickstart](https://docs.debitura.com/clients/getting-started/quickstart-create-first-case.md).

<a id="overview"></a>

## Overview

Webhooks push event data to your endpoint as cases progress through collection. Subscribe to specific events to keep your systems synchronized without repeated API calls.

<a id="available-events"></a>

## Available Events

| Event | Description |
| --- | --- |
| `case.created` | New case submitted |
| `case.updated` | Case lifecycle state changed |
| `case.closed` | Case resolved (paid, written off, or otherwise closed) |
| `payment.created` | Payment registered on a case |
| `payment.deleted` | Payment deleted (reversed) on a case |
| `chat.created` | Chat message added to a case |

<a id="create-a-webhook-subscription"></a>

## Create a Webhook Subscription

You can create webhooks via the API (shown below) or through the Debitura portal. See [Webhooks in the portal](https://support.debitura.com/en/articles/13639813-clients-how-to-use-webhooks-to-get-real-time-case-updates-in-your-own-system-s) for UI instructions.

Create webhook request

```http
POST https://customer-api.debitura.com/webhooks
Content-Type: application/json
XApiKey: YOUR_API_KEY

{
  "url": "https://your-domain.com/webhooks/debitura",
  "events": ["case.created", "case.updated", "case.closed", "payment.created"],
  "isTestMode": false
}
```

**Request body parameters:**

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `url` | string | — | The HTTPS endpoint to deliver events to |
| `events` | string\[\] | — | Event types to subscribe to (see [Available Events](https://docs.debitura.com/clients/integration-guides/webhooks.md#available-events)) |
| `isTestMode` | boolean | `false` | When `true`, this subscription only receives webhook events from test cases. When `false` (default), it receives events from live production cases. Can be changed later via `PATCH /webhooks/{id}` with `isTestMode`. |

Test mode

During integration development, set `"isTestMode": true` to receive events from test cases only — your live production data is unaffected. See [Test vs Live subscriptions](https://docs.debitura.com/developer-concepts/webhooks.md#test-vs-live-subscriptions) for details.

201Created

Response

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://your-domain.com/webhooks/debitura",
  "events": ["case.created", "case.updated", "case.closed", "payment.created"],
  "isActive": true,
  "isTestMode": false,
  "createdUtc": "2026-01-31T15:23:45Z",
  "updatedUtc": "2026-01-31T15:23:45Z",
  "disabledReason": null,
  "secret": "dGhpc2lzYWJhc2U2NGVuY29kZWRzZWNyZXQ="
}
```

Save Your Secret

The `secret` is returned during creation. Store it immediately because list and get responses do not expose it.

To regenerate: `PATCH /webhooks/{id}` with `"regenerateSecret": true`. The PATCH response returns the new secret once; replace your stored value immediately.

<a id="event-payload-structure"></a>

## Event Payload Structure

All events use a CloudEvents-inspired format:

Event envelope structure

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "specVersion": "1.0",
  "event": "case.created",
  "timestamp": "2026-01-31T15:23:45Z",
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W"
  },
  "links": {
    "self": "https://customer-api.debitura.com/cases/123e4567-e89b-12d3-a456-426614174000"
  }
}
```

| Field | Description |
| --- | --- |
| `id` | Unique event ID—use for [idempotency](https://docs.debitura.com/clients/integration-guides/webhooks.md#idempotent-processing) |
| `event` | Event type |
| `timestamp` | When the event occurred (ISO 8601 UTC) |
| `data` | Event-specific payload |
| `links.self` | API URL for the affected resource |

**HTTP Headers:**

| Header | Value |
| --- | --- |
| `X-Debitura-Signature` | `t={timestamp},v1={signature}` |
| `X-Debitura-Timestamp` | Unix timestamp |

The event type is available in the JSON body under the `event` field.

<a id="event-payloads"></a>

## Event Payloads

<a id="casecreated"></a>

### case.created

```json
{
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W",
    "lifecycle": "Pending contract signing"
  }
}
```

| Field | Description |
| --- | --- |
| `caseId` | Debitura's internal case ID |
| `reference` | Debitura case reference (e.g. `Q8OAXF3W`) |
| `lifecycle` | Initial lifecycle state — useful when you need to know whether contracts are still pending without a separate API call. See [Case Lifecycle](https://docs.debitura.com/developer-concepts/case-lifecycle.md#lifecycle-states) for all possible values. |

<a id="caseupdated"></a>

### case.updated

```json
{
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W",
    "oldLifecycle": "Active",
    "newLifecycle": "Pending Verification"
  }
}
```

See [Case Lifecycle](https://docs.debitura.com/developer-concepts/case-lifecycle.md#lifecycle-states) for all lifecycle values and their meanings.

<a id="caseclosed"></a>

### case.closed

```json
{
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W",
    "closeCode": "Paid",
    "closeComment": "Paid in full by debtor"
  }
}
```

See [Case Lifecycle](https://docs.debitura.com/developer-concepts/case-lifecycle.md#close-codes) for all close codes and their meanings.

<a id="paymentcreated"></a>

### payment.created

```json
{
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W",
    "paymentId": "789e4567-e89b-12d3-a456-426614174999",
    "amount": 5000.00,
    "currency": "EUR",
    "date": "2026-01-31T12:00:00Z"
  }
}
```

<a id="paymentdeleted"></a>

### payment.deleted

Fires when a payment is deleted (reversed) on your case — for example a client correcting a payment that was recorded with the wrong amount. Same payload shape as `payment.created`; `date` still reflects the original payment date, not when it was deleted (use the event's top-level `timestamp` for that).

```json
{
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W",
    "paymentId": "789e4567-e89b-12d3-a456-426614174999",
    "amount": 5000.00,
    "currency": "EUR",
    "date": "2026-01-31T12:00:00Z"
  }
}
```

<a id="chatcreated"></a>

### chat.created

For an earlier partner's conversation, `reference` is empty when its case facts are unavailable. Use `caseId` to identify the case.

```json
{
  "data": {
    "caseId": "123e4567-e89b-12d3-a456-426614174000",
    "reference": "Q8OAXF3W",
    "chatId": "456e4567-e89b-12d3-a456-426614174888",
    "role": "Partner",
    "message": "Please provide updated payment plan"
  }
}
```

**Role values:** `Partner` (collection partner), `Creditor` (you), `ManagedByPartner` (partner acting on your behalf)

<a id="signature-verification"></a>

## Signature Verification

Verify every webhook request using [webhook signature verification](https://docs.debitura.com/developer-concepts/webhooks.md#signature-verification). The signature proves the request came from Debitura and wasn't tampered with.

**Quick summary:**

1.  Extract `t` (timestamp) and `v1` (signature) from `X-Debitura-Signature`
2.  Construct: `signed_payload = timestamp + "." + raw_json_body`
3.  Decode your secret from Base64
4.  Compute HMAC-SHA256 of signed payload
5.  Compare signatures using constant-time comparison
6.  Verify timestamp is within 5 minutes

<a id="processing-best-practices"></a>

## Processing Best Practices

<a id="respond-quickly"></a>

### Respond Quickly

Return `2xx` within 10 seconds. Verify the signature, acknowledge immediately, then process the event asynchronously via a queue or background job.

<a id="idempotent-processing"></a>

### Idempotent Processing

Use the `id` field to detect duplicates—Debitura may retry delivery. Store processed event IDs and skip duplicates. See [idempotent webhook processing](https://docs.debitura.com/developer-concepts/idempotency.md#webhook-consumption) for implementation patterns.

<a id="retry-behavior"></a>

### Retry Behavior

Debitura retries failed deliveries with exponential backoff:

| Attempt | Delay | Cumulative |
| --- | --- | --- |
| 1 | 0s | 0s |
| 2 | ~60s | ~1 min |
| 3 | ~120s | ~3 min |
| 4 | ~240s | ~7 min |
| 5 | ~480s | ~15 min |
| 6 | ~960s | ~31 min |
| 7 | ~1800s | ~1 hour |
| 8 | ~1800s | ~1.5 hours |

After 8 consecutive failures (1 initial delivery + 7 retries), the webhook is automatically disabled. Fix your endpoint and re-enable via the API.

A `410 Gone` response immediately disables the webhook with reason `"Endpoint returned 410 Gone (endpoint retired)"`.

<a id="managing-webhooks"></a>

## Managing Webhooks

**List subscriptions:**

List all webhooks

```http
GET https://customer-api.debitura.com/webhooks
```

**Update subscription:**

Update webhook

```http
PATCH https://customer-api.debitura.com/webhooks/{id}

{
  "url": "https://new-endpoint.com/webhooks",
  "isActive": true
}
```

The PATCH body accepts `url`, `isActive`, `isTestMode`, and `regenerateSecret`. Event subscriptions are immutable — sending `events` in a PATCH returns `400` (`WebhookEventsImmutable`). To change which events a subscription receives, delete it and create a new one.

An active subscription must have a unique URL and test/live mode within your account. Changing its URL or mode, or re-enabling it, validates the complete proposed state. A refused PATCH returns `400` with `ValidationError` and a specific reason; the saved subscription stays unchanged. Creating a duplicate active subscription returns `409` with `Conflict`. A missing subscription returns `404` with `WebhookNotFound`. Inactive subscriptions can share a URL.

**Delete subscription:**

Delete webhook

```http
DELETE https://customer-api.debitura.com/webhooks/{id}
```

**Test delivery:**

Test webhook delivery

```http
POST https://customer-api.debitura.com/webhooks/{id}/test
```

Test deliveries include `X-Debitura-Test: true` header.

Action routes use a slash

Webhook action endpoints use a slash sub-route (`/test`, `/replay`). An older colon form (`{id}:test`) still works, but only if you percent-encode the colon as `%3A` (`{id}%3Atest`) — a literal `:` returns `404`. Use the slash form; it is canonical.

<a id="delivery-history--replay"></a>

## Delivery History & Replay

To inspect what's been delivered to a subscription, or to re-deliver a specific event, use the webhook events endpoints:

**List delivered events for a case:**

List webhook events for a case

```http
GET https://customer-api.debitura.com/webhooks/events?caseId={caseId}
XApiKey: YOUR_API_KEY
```

**Replay a specific event:**

Replay a specific event

```http
POST https://customer-api.debitura.com/webhooks/events/{eventId}/replay
XApiKey: YOUR_API_KEY
```

This re-enqueues that exact payload for redelivery to the subscription's current endpoint. The endpoint targets **this specific subscription only** — it does not affect other subscriptions on your account.

Auto-generated API reference: [List webhook events dispatched for a case](https://docs.debitura.com/api/customer-api/webhook-events.md#list-webhook-events-dispatched-for-a-case), [Replay a specific webhook event](https://docs.debitura.com/api/customer-api/webhook-events.md#replay-a-specific-webhook-event).

Deprecated: `POST /webhooks/{id}/replay`

An older, differently-shaped endpoint at this same path only counts `case.created` events since a timestamp — it does not redeliver anything, despite the name. It's kept for backward compatibility but shouldn't be used going forward; use the two endpoints above instead. See the [WebhookEvents reference](https://docs.debitura.com/api/customer-api/webhook-events.md) for details.

<a id="testing-webhooks-in-ci"></a>

## Testing Webhooks in CI

For running automated webhook tests, Debitura provides a full CI loop: create a test case, advance it through lifecycle states, inspect delivery history, and clean up — without pointing at an external receiver. The Customer API is the primary example used throughout that guide. See [CI Testing](https://docs.debitura.com/developer-concepts/webhooks/ci-testing.md) for the full walkthrough and code examples.

<a id="troubleshooting"></a>

## Troubleshooting

**Webhook not received**

Your endpoint must be publicly accessible via HTTPS with a valid TLS certificate. Private IP addresses and localhost are blocked.

**Signature verification fails**

Ensure you're using the raw request body (not parsed JSON) and decoding the secret from Base64 before computing HMAC.

**Webhook disabled**

Check `disabledReason` in the webhook details. Fix the underlying issue, then update with `"isActive": true`.

For a fuller error-message reference, see [Webhook Troubleshooting](https://docs.debitura.com/developer-concepts/webhooks/troubleshooting.md).
