Webhook Delivery Service  ·  View 10 of 20  ·  Data

Data Model

Eight entities, and the one field the consumer contract depends on.

Editable source SVG draw.io All views
tenant tenant_id PK plan status endpoint_quota endpoint endpoint_id PK tenant_id FK -> tenant url state concurrency_cap ordering_mode signing_secret secret_id PK endpoint_id FK -> endpoint wrapped_key active_from active_until subscription subscription_id PK endpoint_id FK -> endpoint event_type_pattern entitlement_scope event event_id PK tenant_id FK -> tenant type occurred_at payload_ref expires_at delivery delivery_id PK event_id FK -> event endpoint_id FK -> endpoint idempotency_key state next_attempt_at attempt attempt_id PK delivery_id FK -> delivery attempt_no outcome_class status_code duration_ms dead_letter delivery_id PK, FK -> delivery final_outcome dead_at expires_at replayed_as 1 : N 1 : 2 1 : N 1 : N 1 : N 1 : N 1 : N 1 : 0..1 Data Model The idempotency key is stable across every attempt and every replay of a delivery. v 1.0 · owner Integration Platform Architecture · date 2026-09

The load-bearing fields

  • `delivery.idempotency_key` is stable across every attempt and every replay of that delivery. It is what makes at-least-once safe for the consumer, and it is the field the published contract names (ADR-04).
  • `endpoint.ordering_mode` is a declared property, not a platform-wide default. A subscription that wants strict per-endpoint ordering accepts head-of-line blocking; one that does not, is not silently given it (ADR-05).
  • `attempt.outcome_class` is one of exactly four values. The taxonomy is published, because a developer's first question is which class their failure landed in.
  • `dead_letter.replayed_as` links a dead letter to the delivery that replaced it, so a replay is traceable rather than a second mystery.

Deliberate shape

  • Two signing secrets per endpoint, bounded by `active_from`/`active_until` — rotation is a data property, not an operational procedure (ADR-07).
  • `event.expires_at` and `dead_letter.expires_at` are TTL attributes, so retention is enforced by the store rather than by a job that can stall.

Not modelled

  • Endpoint health state. It is recomputed from recent attempts rather than stored as truth, so a stale health record can never be the reason a healthy endpoint is not delivered to.
  • Payload bytes. They live in object storage under `event.payload_ref`; the model holds the pointer.