# Headers

Every header on a delivery, and what to do with it.

Every delivery carries the same seven headers. Nothing else is sent, and no
header is ever omitted.

```http
POST /webhooks/salaf HTTP/1.1
Content-Type: application/json
User-Agent: Salaf-Webhooks/1.0
X-Salaf-Signature: t=1754899200,v1=dd3c17ef08611a833fd18718fb90d48d278db0f7f1ced1c1812aad359a718de8
X-Salaf-Event: order.created
X-Salaf-Event-Id: evt_01J4X8G9ABCDEFGHJKMNPQRSTV
X-Salaf-Delivery: whd_01J4X8GB2CDEFGHJKMNPQRSTVW
X-Salaf-Api-Version: 2026-08
```

| Header                | Value                          | What to do with it                                                                                                                                                                           |
| --------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Salaf-Signature`   | `t=<unix>,v1=<hex>[,v1=<hex>]` | **Verify it before reading the body.** Two `v1` values mean a rotation is in progress; accept either. See [Verifying signatures](https://www.salafems.com/developers/webhook-signatures.md). |
| `X-Salaf-Event`       | `order.created`                | Route on it if it saves you parsing the body first. It is always identical to `type` in the payload.                                                                                         |
| `X-Salaf-Event-Id`    | `evt_` + ULID                  | **Your de-duplication key.** Identical to `id` in the payload. Stable across every retry of the same event.                                                                                  |
| `X-Salaf-Delivery`    | `whd_` + ULID                  | This *attempt's* record. Changes between retries, unlike the event id. Quote it to support; never de-duplicate on it.                                                                        |
| `X-Salaf-Api-Version` | `2026-08`                      | The payload version pinned to your endpoint. Identical to `api_version` in the body.                                                                                                         |
| `Content-Type`        | `application/json`             | The body is always JSON, always UTF-8.                                                                                                                                                       |
| `User-Agent`          | `Salaf-Webhooks/1.0`           | Useful in access logs. **Not** an authentication signal — anyone can send it.                                                                                                                |

> **Warning — Event id and delivery id are not the same thing**
>
> One event fanned out to three endpoints produces three deliveries, and each
> retry of any of them keeps its own delivery id. So:
>
> - **De-duplicate on `X-Salaf-Event-Id`.** It identifies the *fact*, and it is
>   the same on every retry — which is exactly what makes at-least-once
>   delivery safe.
> - **Log `X-Salaf-Delivery`.** It identifies the *attempt*, and it is what
>   matches a row in the merchant's delivery history when you need someone to
>   look at their side.
>
> A receiver that de-duplicates on the delivery id de-duplicates nothing: every
> retry has a fresh one, so every retry runs again.

## What is not sent

- **No authentication header.** The signature is the authentication. Do not
  expect a bearer token, and do not put a secret in your webhook URL's query
  string — it would end up in logs and proxies. If your receiver needs an extra
  layer, put it behind mTLS or an allow-list, not a URL secret.
- **No custom headers.** There is no way to configure additional headers on an
  endpoint.
- **No compression or chunking.** Bodies are small and sent whole.

## Your response

Only the status line is interpreted. `2xx` is success; everything else is a
failure that gets [retried](https://www.salafems.com/developers/webhook-retries.md).

The first kilobyte of your response body **is** stored, and shown to the
merchant in the delivery log — so a plain-text error message from your handler
is genuinely useful there. Header-shaped lines (`Set-Cookie:`,
`Authorization:`, `X-Api-Key:` and friends) are stripped before storage, in
case a framework echoes the request back in a debug response; but the simplest
protection is not to echo secrets in an error body in the first place.

Response headers you send are not read at all — including `Retry-After`. The
retry schedule is ours and is not negotiable per delivery.
