# Signed webhooks for everything else

> Chosen ledger events to your own endpoint, signed with HMAC-SHA256 over a timestamp, with a replay window and a delivery id.

Source: https://immiscible.fly.dev/docs/guides/signed-webhooks

For a tool with no card here, register a webhook (the Signed webhook card opens **SIEM and webhooks**, under **Settings**, **For engineers**). It is the same mechanism [the SIEM export](https://immiscible.fly.dev/docs/guides/siem-export.md#push-signed-webhooks) describes, in short:

- **Signature.** `immiscible-signature: t=<unix seconds>,v1=<hex>`, where `v1` is HMAC-SHA256 of `<t>.<raw body>` with the webhook's secret (shown once).
- **Replay window.** Refuse a delivery whose `t` is more than 300 seconds from your clock.
- **Replays inside the window.** Each delivery carries `immiscible-delivery: dlv_...`; keep the ids you have seen for ten minutes and drop repeats.
- **Events.** A list of ledger kinds (for example `agent_freeze`, `agent_incident`, `accounting_export`), or `["*"]`; as OCSF 1.3 or the native record. A kind the ledger never writes is refused, with the closest real one named.
- https only, never a private address.

## Retries and resends

Two different things, and your endpoint can tell them apart:

| | Automatic retry | Manual resend |
|---|---|---|
| When | the endpoint did not answer 2xx | a person (or your script) asks for it, with [`POST .../deliveries/:did/resend`](https://immiscible.fly.dev/docs/api/post-api-w-wid-webhooks-id-deliveries-did-resend.md) |
| How many | up to five attempts in all, waiting 2, 8, 32 and 128 seconds between them | one attempt, at once, not retried |
| `immiscible-delivery` | the **same** id on every attempt, so drop it if you already processed it | a **new** id; the delivery list shows its `resentFrom`, the id it repeats |
| Body | the same | the same, so its `record.id` (or OCSF `metadata.uid`) is the same |
| Signature | signed afresh with a new `t` each attempt | signed afresh |

So: drop a repeated `immiscible-delivery` id as a duplicate; treat a new id carrying a record you have already seen as a deliberate resend, and decide by the record's own id whether to process it again.

## Changing a webhook

[`PATCH .../webhooks/:id`](https://immiscible.fly.dev/docs/api/patch-api-w-wid-webhooks-id.md) changes `url`, `events` or `format` and keeps the secret. [`POST .../webhooks/:id/rotate-secret`](https://immiscible.fly.dev/docs/api/post-api-w-wid-webhooks-id-rotate-secret.md) makes a new secret, shown once; the old one stops at once, retries included, so update your verifier first or expect a few refusals. Each delivery in the list keeps the headers it was sent with (`request.headers`: the signature, never the secret), the SHA-256 of its body, and the status and first 1 KB of what your endpoint answered.

Verification code in TypeScript and Python is in [Verifying a delivery](https://immiscible.fly.dev/docs/guides/siem-export.md#verifying-a-delivery).
