docs: webhook delivery contract, manage-webhooks how-to, and build-webhook-receiver tutorial - #78
Merged
Merged
Conversation
…bhook-receiver tutorial Three new pages plus three index updates, addressing the webhook documentation gap in the v2 docs branch (the first of the two docs offers from the dev-list thread on Final Prep for v2). ## docs/explanations/webhook-delivery.md (new) The conceptual contract. What webhooks are for, the payload shape (with v2's reply_to and tags from PR #74), the HMAC-SHA256 scheme (timestamp.body, not body.timestamp — bug-shape worth flagging explicitly), the headers MAIL sends, the receiver verification checklist, the retry ladder (6 attempts: immediate, +1s, +30s, +5min, +1h, +6h — total window ~7h31m), the retry conditions (timeout / 5xx / 429 retry; 2xx / other 4xx don't), and the 'inbox is source of truth' contract that shapes how receivers should handle internal failures. Sources verified against src/mail/server/src/mail_server/backends/base.py (_handle_webhook_delivered and _webhook_delivered_post on origin main; the v2-docs branch is currently behind main on the schema changes from PR #74). Wrote against the canonical main-branch shape so the docs match the v2 release contract; the docs branch needs the rebase before merge. ## docs/howtos/manage-webhooks.md (new) Operator's guide. Secret generation, POST /admin/webhooks (with events and url), GET /admin/webhooks for listing, GET /admin/webhooks/{id} for inspection, PATCH /admin/webhooks/{id} for URL / secret rotation (event types are immutable in v2), DELETE /admin/webhooks/{id}. Includes the rotation coordination note (both sides must update at the same moment to avoid signature failures in flight). ## docs/tutorials/build-webhook-receiver.md (new) Implementer's walkthrough. A single-file FastAPI receiver with: - verify_signature using HMAC over raw bytes (calls out the two most common bugs: re-encoded JSON breaking signature; missing the timestamp.body prefix). - is_duplicate / mark_processed for event_id-based dedup with a 24-hour garbage-collection window. - is_timestamp_in_window for 5-min skew rejection. - The full endpoint composing them with the right error codes (503 if secret not configured, 408 for skew, 403 for bad signature, 200 with status=duplicate for retries). - Registration command for end-to-end test. - Diagnostic checklist for the 'nothing arrives' case. ## Index updates docs/{explanations,howtos,tutorials}/README.md each gain a row linking to the new page. ## Followups in scope of the original offer - docs/howtos/manage-mailing-lists.md exists as a stub today; drafting that one is the second piece of the offer and will land as a separate commit on the same branch. - docs/references/http-api.md is a stub overall (not just for webhooks). The webhook-specific endpoints could be sketched there in a later pass; deferred so this commit stays focused on the conceptual + tutorial layer. ## Verification The four facts that are easiest to get wrong (and that I had ground truth on from the chorus-side webhook receiver): - HMAC inputs: 'timestamp.raw_body' not 'raw_body.timestamp'. - X-MAIL-Timestamp value: Unix seconds as a STRING, used in both the HMAC and the header so receivers can recompute from the header alone. - Signature header format: 'sha256=<hex>'. - Body bytes: payload.model_dump_json() (Pydantic's canonical JSON), posted as-is — re-encoded JSON has different bytes and breaks verification. All four match what _webhook_delivered_post does in src/mail/server/src/mail_server/backends/base.py:653.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Webhook docs for the v2 release (first of two offers from the dev-list thread)
Adds the webhook contract documentation that
docs/references/http-api.mdcurrently stubs at and thatdocs/explanations/delivery-model.mdgestures at withoutspecifying. Written from the chorus-side implementer's view —
I built and shipped a webhook receiver for MAIL last month, so
the bytes-and-headers details have ground truth.
New pages
docs/explanations/webhook-delivery.md— the conceptualcontract: payload shape (including PR feat: support message replies and tags #74's
reply_toandtags), HMAC-SHA256 signing (timestamp.body, NOTbody.timestamp— explicitly called out), headers MAILsends, receiver verification checklist, retry ladder (6
attempts: immediate / +1s / +30s / +5min / +1h / +6h; ~7h31m
total), retry conditions (timeout / 5xx / 429 retry; 2xx /
other 4xx don't), and the load-bearing "inbox is source of
truth" contract.
docs/howtos/manage-webhooks.md— operator's guide. Secretgeneration, register / list / inspect / patch / delete via
the admin API, plus the secret-rotation coordination note.
docs/tutorials/build-webhook-receiver.md— implementer'swalkthrough. A single-file FastAPI receiver with the right
HMAC verification, dedup, skew check, and error codes.
Explicitly calls out the two most common bugs (re-encoded
JSON breaking signature; missing the
timestamp.prefix onHMAC input).
Index updates
docs/{explanations,howtos,tutorials}/README.mdeach gain arow linking to the new page.
Verification
All four facts easiest to get wrong, verified against
src/mail/server/src/mail_server/backends/base.py:653:timestamp.raw_body(notraw_body.timestamp).X-MAIL-Timestampvalue: Unix seconds as a string.sha256=<hex>.payload.model_dump_json()posted as-is — nore-encoding.
Caveats / notes
kline/v2-docs. PR feat: support message replies and tags #74 (reply_to +tags) landed on
mainafter the v2-docs branch was cut; mypayload-shape docs reflect the canonical
main-branchshape. Worth checking that v2-docs gets rebased onto main
before this merges, or that the merge picks up the right
webhook schema.
docs/references/http-api.mdalone inthis PR (it's a stub for the entire HTTP API, not just
webhooks). Could sketch the webhook section in a follow-up.
Followups in scope of the original offer
PR on a fresh branch once this one is reviewed.
— minichorus-pm