Merchant webhook onboarding (AuthWeave asymmetric Standard Webhooks)¶
AuthWeave verifies deliveries; it does not own merchant inbox or business idempotency. Private signing keys stay with the webhook sender. Consumers only receive public keys.
Onboarding binding¶
Each binding is exact:
environment(sandbox|live)owner/ merchant id- HTTPS
endpointURL - public key document (≤3 Ed25519 keys: active / next / retiring)
- optional key-set URL for one controlled refresh
Sandbox and live never share keys. Independent merchants never share keys (ADR 0002).
Checked sandbox and live templates with explicit active/next/retiring roles
live under
environment-packs/webhooks/. They
contain disposable example public keys only; production packs and private keys
must stay in the approved configuration/KMS systems.
Consumer checklist¶
- Read the raw body bytes before JSON parsing.
- Verify
webhook-id,webhook-timestamp, andwebhook-signaturewithStandardWebhooksVerifier. Its mandatory shared replay store atomically claims the delivery only after a valid signature, namespaced by environment, owner, endpoint, andwebhook-id; outage and capacity pressure fail closed. A repeated valid delivery returns a verified envelope withreplay_detected=True. - After every successful verification, atomically insert the complete raw
body and verified metadata into a durable inbox under a unique
environment/owner/endpoint/
webhook-idkey. Use insert-if-absent semantics; never overwrite the first committed item. A retry therefore repairs the claim-before-inbox crash window instead of losing the event. - Return
2xxonly after the inbox transaction commits. Process and retry business work from the inbox;replay_detectedis telemetry and must not replace durable idempotency. - On sender retry, keep the same
webhook-idand create a delivery with a fresh timestamp + signature; do not treat a new timestamp as a new business event.
The verifier owns the complete timestamp-window replay TTL. Consumers cannot
shorten it. InMemoryReplayStore is only for tests or a documented single-worker
process; production consumers use a worker-shared implementation such as
RedisReplayStore. The durable inbox remains the authoritative delivery record.
Key compromise / rotation¶
- Rotate by publishing
next, then retiring the old key inside the ≤3-key window. - If a sender private key is compromised, revoke/retire the matching public key immediately in a monotonically newer merchant document and force refresh. A compromised key is not retained for overlap.
- Consumers never store sender private or shared HMAC secrets for this profile.
Use the full
webhook-key-compromise runbook. Its
KMS/merchant/infra-owner exercise and external cryptographic review remain
production gates until their evidence is attached.
Sender SSRF / egress boundary¶
HttpxWebhookSender requires a non-empty exact endpoint allowlist, never
follows redirects, and streams at most 65,536 response bytes. URL checks do not
defeat DNS rebinding or a compromised allowlisted host. A controlled egress
proxy or isolated subnet is mandatory; direct egress is not a fallback. See the
sender threat model for deployment controls
and negative checks.
Language-neutral vectors¶
See the Standard Webhooks vector manifest. Run:
uv run --package authweave-webhooks python docs/vectors/webhooks/v1a/verify_vectors.py
node docs/vectors/webhooks/v1a/verify_vectors.mjs
Both commands consume the same exact signed bytes, active/next/retiring overlap,
and negative cases. The Node.js verifier uses only the independent built-in
Ed25519 implementation. The manifest pins Standard Webhooks upstream tag
v1.0.2.