DocumentsBuilding on it

Webhooks: signed pushes, and exactly what is guaranteed

2 min readSource webhooks.mdsha256 7d1305474ba55037…

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 webhooks scope.
  • What the endpoint must be: https, a public DNS name, no credentials in the URL. IP literals, localhost, .internal and 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-delivery
  • record.id is 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.to is your member kid. Refuse anything addressed to someone else.
  • record.payload is 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#

PropertyWhat holds
DeliveryAt least once. Dedupe on id.
OrderingNot guaranteed.
RetriesAttempts 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 GoneRetires the endpoint: the subscription is paused. Re-subscribe to reactivate; /webhooks/redeliver requeues a dead or gone delivery.
First attemptStarts 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.
DurabilityThe 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 isA 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/export on a schedule (see SIEM export);
  • or a relay inside the bank verifies deliveries and forwards them with the SIEM's token.