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

# Delivery semantics

> Retry schedule, idempotency, and receiver requirements.

## At-least-once delivery

Use the `id` field on the envelope for idempotency. If the same event
arrives twice, treat the second as a no-op. We retry on transient failures,
so duplicates are expected but rare.

## Retry schedule

| Attempt         | Delay from previous                                                      |
| --------------- | ------------------------------------------------------------------------ |
| 1               | Immediate (≤ 5 seconds after the event)                                  |
| 2               | +1 second                                                                |
| 3               | +30 seconds                                                              |
| 4               | +5 minutes                                                               |
| After attempt 4 | Marked `failed`. Admins can retry manually from our partner detail page. |

Each attempt has a **10-second** HTTP timeout. Anything else than a 2xx
response, a network error, or a timeout counts as a failure.

## Concurrency

Webhooks are dispatched in parallel. You may receive events out of
`createdAt` order. If ordering matters for your processing, sort on your
side before applying.

## Receiver checklist

<Check>HTTPS endpoint, public, ideally idle latency ≤ 200 ms.</Check>
<Check>Verify the signature **before** parsing or trusting the body.</Check>
<Check>Return 2xx as soon as the event is enqueued — don't do heavy work in-band.</Check>
<Check>Dedupe on `event.id` — we will retry on transient failure.</Check>
<Check>Treat unknown `event.type` values as a no-op so you don't 500 when we add new ones.</Check>

## What we send on each retry

Each retry signs a **fresh** timestamp, so the `x-signature` header
changes on every attempt — don't try to dedupe by signature, only by
`event.id`. The body bytes stay identical.

## Manual retry

If an event ages out of automatic retries (4 attempts failed), our admins
can re-arm it from the partner detail page. This resets the attempt counter
and `failedAt`, and the dispatcher picks it up on the next tick. Tell us
the `event.id` (we have it in our logs too) and we can re-queue it for you.

## Disabling a webhook

There is no "pause" toggle today. If you need to halt deliveries (e.g.
your endpoint is down for planned maintenance), the practical options are:

1. Have your endpoint return HTTP 503 for the duration. We'll retry up to 4
   times per event, then mark them failed; admins can re-queue after.
2. Ask us to disable your partner record temporarily. New events will still
   be recorded but not dispatched, and JWT verification will reject your
   tokens with `partner_disabled` (403) for the duration.
