deino-whisper: HMAC-signed webhooks that survive a retry storm
The delivery worker, the WhisperStore port, and why a webhook is an audit artifact.

A webhook is a delivery, and a delivery is a claim. The recipient needs to know the payload came from you, unmodified, at the time you sent it — and you need to know it arrived, or that you tried. deino-whisper is the webhook system that treats delivery as an audit artifact, not a fire-and-forget.
Whisper sends HMAC-signed webhooks: each payload is signed with a key the recipient verifies, so a tampered payload fails verification and a forged delivery is detectable. The delivery worker holds a WhisperStore port — a trait, not a concrete pool — so the persistence of delivery state is behind an interface tests can mock (INV-5). A webhook is not a notification; it is a signed, recorded, retryable audit artifact.
The WhisperStore port
deino-whisper owns a private table — the delivery queue and the delivery log — and because that table belongs to one slice, it does not go in deino-ledger. It goes in a slice-owned port: WhisperStore, the trait, with PgWhisperStore, the adapter, colocated in the same crate. The worker holds Arc<dyn WhisperStore>, so tests swap an in-memory store and the real deployment uses Postgres. The port is the boundary; the adapter is the implementation; the slice never touches sqlx for behavior (INV-6).
HMAC: the signature is the provenance
HMAC is the signing scheme. The payload is signed with a shared secret; the recipient verifies the signature against the same secret. A payload that passes verification came from someone with the secret and was not modified in transit. The signature is the provenance: it attests sender and integrity in one field, and it is what turns a webhook from a notification into evidence. The manifest records the signature; the recipient verifies it; the chain is closed.

A webhook is a delivery, and a delivery is a claim. The signature is what turns a notification into evidence.
Surviving a retry storm
Webhooks fail. The recipient is down, the network drops, the endpoint returns a 500. A naive delivery system retries blindly — and a retry storm is what happens when retries pile up faster than the recipient recovers. deino-whisper retries with backoff and a recorded state machine: each delivery attempt is logged, the next attempt is scheduled, and the log is the record. A recipient that recovers sees the queued deliveries in order, signed, with the attempt history. The retry storm is bounded because the retries are stateful, not blind.
- HMAC-signed payloads — the recipient verifies sender and integrity in one field; a forged delivery is detectable.
- WhisperStore port (slice-owned) — the delivery queue is behind a trait, tests mock it, the slice never touches sqlx for behavior (INV-6).
- Stateful retries with backoff — each attempt is logged; a retry storm is bounded because retries are recorded, not blind.
- A webhook is an audit artifact — the signature, the attempt history, and the payload are in the manifest.
Why a webhook is an audit artifact
In a regulated context, the question did you deliver the forecast to the system of record is a question an examiner asks. A fire-and-forget webhook cannot answer it — there is no record of the attempt, let alone the result. deino-whisper records the delivery: the signed payload, the attempt history, the final status. An auditor points at the record, not at an assertion. We are pre-revenue, building the POC; the whisper worker runs, and the delivery log is the artifact that answers the delivery question when it is asked.
Fire-and-forget is a delivery that cannot prove it happened. A signed, recorded, retryable delivery is an audit artifact that can. The difference is the difference between a notification and evidence.
— deino-whisper design note