When a verdict is signed on a shipment where your leg authenticated, the operator pushes it to your endpoint. When a witness your clears rested on is suspended, you get your own signed recall notice. Nothing is pushed to a member that is not provably a party.
Subscribe#
curl -X POST $OPERATOR/webhooks/subscribe -H "Authorization: Bearer <member session>" \
-d '{"url":"https://mirror-hooks.bank.example/deliveries","events":["verdict.signed","recall.notice"]}'
Or use client.webhooks.subscribe(url) in the SDK, or the Terraform module.
- A member session is required. The shared pilot token names no institution to deliver to. A desk key needs the
webhooksscope. - What the endpoint must be: https, a public DNS name, no credentials in the URL. IP literals,
localhost,.internaland single-label names are refused. The Node host re-checks the resolved address at connect time, so a name re-pointed at a private address is refused too. Redirects are not followed. - What you get back: a signed receipt of exactly what you registered.
- Idempotent: subscribing the same endpoint again is the same subscription.
What arrives#
POST to your URL, body {record, proof}:
record.type=mirror.webhook-deliveryrecord.idis the delivery id, to dedupe on. It is stable across retries and across a retried or failover-resubmitted verdict, because it is derived from the judgement, not the signature bytes (SPEC §8.2).record.tois your member kid. Refuse anything addressed to someone else.record.payloadis the verdict's own{record, proof}, verifiable on its own.- Headers:
Mirror-Delivery,Mirror-Event,Mirror-Attempt.
Verify with mirror-webhook.verifyDelivery(body, {jwks, me, now_ms, seen}), or client.webhooks.verify(). It checks:
- the operator's signature;
- the recipient;
- freshness (5 minutes);
- the payload's own signature;
- that the id derives from the subscription and the record;
- dedupe.
Answer 2xx, including for a duplicate.
The guarantees, exactly#
| Property | What holds |
|---|---|
| Delivery | At least once. Dedupe on id. |
| Ordering | Not guaranteed. |
| Retries | Attempts at 0 s, 30 s, 2 m, 10 m, 1 h, 6 h and 24 h, each plus up to 10% deterministic jitter. Then dead, and still listable. |
410 Gone | Retires the endpoint: the subscription is paused. Re-subscribe to reactivate; /webhooks/redeliver requeues a dead or gone delivery. |
| First attempt | Starts as soon as the verdict is signed. Measured on the Node host over loopback in the test suite: under 2 seconds. On the Worker it runs in the same invocation, after the response. |
| Durability | The outbox is the durable store (mirror_webhook_deliveries). Retries run on the Node host's timer and the Worker's one-minute cron trigger. With an in-memory store, a pending retry lives only as long as the process. |
| What it is | A notification. The ledger is the record of truth; a lost push loses nothing. |
SIEM#
A SIEM collector wants its own token, and an operator holding it would hold a credential into the bank. So the operator stores none. Either:
- the SIEM pulls
/audit/exporton a schedule (see SIEM export); - or a relay inside the bank verifies deliveries and forwards them with the SIEM's token.