Source: https://docs.debitura.com/developer-concepts/webhooks/strategy

# Webhook & Test-Mode Strategy

This page is the single source of truth for the webhook and test-mode design across Debitura's public APIs. It covers how test and live subscriptions work, the CI loop primitives available on each API, and where each auxiliary capability fits.

* * *

<a id="core-design-principle"></a>

## Core design principle

Core CI-loop primitives are **symmetric across the Customer API and Collection Partner API**. Auxiliary primitives exist where they are genuinely needed, not for symmetry's sake.

* * *

<a id="test-vs-live-subscriptions"></a>

## Test vs live subscriptions

See [Test vs Live Subscriptions](https://docs.debitura.com/developer-concepts/webhooks.md#test-vs-live-subscriptions) for how `isTestMode` works, switching a subscription between modes, and running test and live subscriptions simultaneously.

* * *

<a id="the-4-step-ci-loop"></a>

## The 4-step CI loop

The complete test loop follows four steps:

```text
CREATE  →  DRIVE  →  ASSERT  →  RESET
```

1.  **CREATE** — create a test case with `isTest: true` and a run-scoped `tag`
2.  **DRIVE** — call `advance` to move the case to a target lifecycle state
3.  **ASSERT** — call `GET /webhooks/events?caseId={id}` to verify what fired and was delivered
4.  **RESET** — call `DELETE /test/cases?tag={tag}` to hard-delete all run data

See [CI Testing](https://docs.debitura.com/developer-concepts/webhooks/ci-testing.md) for the full implementation with code examples.

* * *

<a id="per-api-capability-matrix"></a>

## Per-API capability matrix

<a id="core-ci-loop--symmetric-customer-api--collection-partner-api"></a>

### Core CI loop — symmetric (Customer API + Collection Partner API)

| Primitive | Customer API | Collection Partner API | Notes |
| --- | --- | --- | --- |
| Create test case | `POST /cases` with `isTest: true` | `POST /managed-cases` with `isTest: true` | Tag the case for scoped cleanup |
| Advance lifecycle | `POST /test/cases/{id}/advance` | `POST /test/cases/{id}/advance` | Same request body |
| Hard-delete by tag | `DELETE /test/cases?tag=` | `DELETE /test/cases?tag=` |  |
| Hard-delete by ID | `DELETE /test/cases/{id}` | `DELETE /test/cases/{id}` |  |
| Delivery history | `GET /webhooks/events` | `GET /webhooks/events` | Filter by `caseId` and `since` |
| Replay by event ID | `POST /webhooks/events/{id}/replay` | `POST /webhooks/events/{id}/replay` | Re-enqueues the exact original payload |

<a id="fire--symmetric-customer-api--collection-partner-api"></a>

### fire — symmetric (Customer API + Collection Partner API)

`POST /test/webhooks/fire` fires a named event type for a test case without advancing the lifecycle. Use it to test your handler in isolation — emit `case.closed` without actually closing the case.

Valid event types differ by API because the event vocabularies differ:

| API | Valid event types |
| --- | --- |
| Customer API | `case.created`, `case.updated`, `case.closed`, `payment.created`, `payment.deleted`, `chat.created` |
| Collection Partner API | `case.assigned`, `case.declined`, `case.updated`, `case.closed`, `payment.created`, `payment.deleted`, `chat.created`, `managed_case.created`, `managed_case.assigned`, `managed_case.updated`, `managed_case.closed`, `managed_case.declined`, `managed_case.payment.created`, `managed_case.payment.deleted`, `managed_case.chat.created` |

note

Managing events require a test case you manage and a test subscription selecting the event. `payment.created` returns `422` if no payments exist on the case. `payment.deleted` returns `422` if no deleted payments exist on the case. `chat.created` returns `422` if no chats exist.

<a id="replay-last-event--collection-partner-api-only-by-design"></a>

### replay-last-event — Collection Partner API only (by design)

`POST /test/cases/{id}/replay-last-event` replays the most recent webhook event for a case without needing to look up the event ID.

This endpoint is Collection Partner-only and is intentionally not ported to the Customer API. It was added as a stopgap when the Collection Partner API lacked a delivery history endpoint. Now that `GET /webhooks/events` is available on both APIs, `replay-last-event` is redundant — but it is kept for backward compatibility and documented here as a thin convenience wrapper over `GET /webhooks/events` + `POST /webhooks/events/{id}/replay`.

Use `replay-by-id` in new integrations. Use `replay-last-event` only when you genuinely don't want to enumerate event history.

<a id="referral-partner-api--separate-model"></a>

### Referral Partner API — separate model

Referral Partner webhook subscriptions support `isTestMode`; synthetic test events target test subscriptions. `POST /v1/webhooks/test` remains restricted to the dedicated test environment at `testreferral-api.debitura.com`.

The Customer API and Collection Partner API CI-loop primitives described above do not apply to the Referral Partner API.

* * *

<a id="other-webhook-capabilities"></a>

## Other webhook capabilities

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

### Signature verification

Every delivery includes an `X-Debitura-Signature` header for HMAC-SHA256 verification. See [Signature Verification](https://docs.debitura.com/developer-concepts/webhooks.md#signature-verification) for implementation details and code examples.

<a id="retry-and-auto-disable"></a>

### Retry and auto-disable

Failed deliveries use up to 8 total delivery attempts with exponential backoff. A subscription is automatically disabled after all 8 attempts fail, or if your endpoint returns `410 Gone`. See [Retry Behavior](https://docs.debitura.com/developer-concepts/webhooks.md#retry-behavior) for the full schedule.

<a id="idempotency"></a>

### Idempotency

The event `id` field in every payload is a stable UUID. Track it to deduplicate replayed or retried deliveries. Replayed events also include the header `X-Debitura-Replay: true`. See [Idempotency: webhook consumption](https://docs.debitura.com/developer-concepts/idempotency.md#webhook-consumption).

<a id="local-development"></a>

### Local development

Use a tunneling tool (ngrok, cloudflared) to expose a local port and point a test subscription at it. See [Local Development](https://docs.debitura.com/developer-concepts/webhooks.md#local-development).
