Free learning / 50-minute webhook proof workshop
Webhook verification: signed delivery is not new business work
Verify exact signed bytes, distinguish authentic stale messages from fresh repeats, and bind a business receipt to one local effect through ten offline HMAC and SQLite cases.
No account needed. Use your own copy for writing and keep the original as a reference.
Engineering lab library / Run locally
Choose your next agent lab.
Start with the durable workflow reference, then choose the boundary you want to test. Open its walkthrough for setup, commands and expected observations.
Synthetic records. No provider key or model inference. Runs locally with the standard library.
Start here / Python 3.11+ · Standard library
Webhook byte verification and business event receiving
Compare authentic, tampered, stale, repeated and conflicting deliveries against actual local receipt/effect state.
Python 3.11+ · Standard-library HMAC and SQLite · Ephemeral keys · No network
Original LockedIn Labs workshop · 50 minutes · Python 3.11+ standard library. Ten offline cases verify actual HMAC-SHA256 values and execute scratch SQLite transactions. Each case generates fresh ephemeral keys; neither keys nor MAC values appear in reports. There is no HTTP endpoint, provider account, real event, persistent database or production authentication capability.
1. Give each boundary its own question
A webhook is a source sending a notification to a receiver, commonly through HTTP. Before acting, the receiver needs to know which configured source contract applies, whether the received bytes authenticate under that contract, whether their signed time is permitted, whether the payload is supported, and whether the business event was already applied. A successful connection answers none of those questions.
An HMAC, or keyed-hash message authentication code, binds bytes using a shared secret. Verification demonstrates agreement with the configured key and message bytes; it is not encryption, a public-key signature or proof of which individual held the shared key. A valid MAC does not grant end-user access or make the reported business change appropriate.
The source in this exercise is selected by receiver configuration, not by an unverified request field. It reports a fictional synthetic.delta of four units. The effect is a counter in one disposable memory database, not a payment, account update, clinical action or external command.
Illustrative learning artifact
One event. Two transaction boundaries.
event-42 → delivery may repeat →
2. Verify the exact envelope before parsing JSON
This workshop uses an original teaching envelope. Its internal lab-delivery-id, lab-sent-at and lab-mac names are not a provider integration. The sender computes HMAC-SHA256 over:
delivery_id ASCII bytes + "." + sent_at ASCII bytes + "." + exact body bytesThe raw body contains a separate stable event_id. The delivery ID identifies an attempt in this fixture; a later attempt can have another ID and send time while reporting the same business event. Real providers assign different semantics to their headers and IDs. Use their supported SDK and exact documented scheme; do not substitute these lab fields into a real endpoint.
The receiver bounds the body and header shapes, decodes one version-one Base64 MAC into exactly 32 bytes, and compares it with the full expected digest using hmac.compare_digest. It does not choose the comparison length from a supplied digest. It verifies the signed timestamp policy before parsing JSON or opening a receipt transaction.
A JSON parser can treat differently spaced documents as the same object. That does not make their bytes identical. Verifying a reserialized object instead of the original bytes changes the authenticated message. Keep raw bytes at the real HTTP/framework boundary according to the source contract; this offline program receives bytes directly and does not test middleware behavior.
UNDERSTAND THE SYSTEM
Verify the delivery, then receive the event once
Six authored frames explain ten offline HMAC/SQLite comparisons. The original envelope binds delivery ID, timestamp and exact body bytes. Ephemeral keys and MAC values are omitted from reports; this is not a live endpoint or a provider implementation.
Watch one example, step by step.
Read the full worked example
- 011 · Exact received bytes are authenticated
The valid case checks the configured key, full MAC, signed delivery ID/time and original body. It then parses one supported payload and commits one receipt plus four local counter units. This is an original offline contract, not a deployed provider endpoint.
ACTUAL VALID COMPARISON: One MAC comparison · one parse · one receipt · four units
- 022 · Equal JSON meaning is not equal signed bytes
Changing units, reserializing spacing, using another key or changing the delivery ID without re-signing fails MAC verification. The reserialized body has the same JSON meaning, but different bytes. These rejected attempts do not reach receiver JSON parsing or receipt state.
ACTUAL BYTE/KEY MISMATCHES: MAC mismatch · zero receiver parses · zero effects
- 033 · A valid MAC can still be too old or too far ahead
Stale and future carry authentic envelopes but signed times outside the fixture's -30 to 300 second policy. Both are refused before JSON parsing. The clock is fixed fictional input, not measured wall time; real clock and skew policy remain owner responsibilities.
ACTUAL TIME-POLICY REFUSALS: MAC valid · age 301 / -31 refused
- 044 · A freshly signed repeat is still the same event
Repeat signs DEL-2 at a new time while retaining EVT-1 and the identical raw body. Both deliveries verify and parse. The second finds the source/event receipt and replays it; the counter remains four. MAC bytes, send time and attempt ID are not this contract's business receipt key.
ACTUAL REPLAY COMPARISON: Two valid deliveries · one receipt · four units
- 055 · Authentication does not override a receipt conflict
Event-conflict re-signs changed units. Same-event-new-bytes re-signs different spacing with unchanged parsed meaning. Both MACs pass, but the exact-byte receipt contract conflicts under EVT-1. The first four-unit state remains. Byte conflict is not a claim that whitespace changed the business meaning.
ACTUAL STRICT RECEIPT BINDING: Valid new MAC · changed body hash · first state retained
- 066 · Change signed age without widening the policy
Change only the valid case's send age from zero to 301 seconds. The sender creates a valid MAC for those bytes and fields; the unchanged receiver policy refuses its time. No JSON parse or receipt follows. A freshness decision cannot replace event idempotency, and neither establishes business permission.
CONTROLLED SIGNED-TIME CHANGE: Age 0 applies · age 301 is refused
Read the connections in plain English
- 01Received byte envelope → Configured-key verification
Changing a signed field or using another key fails the original envelope.
- 02Configured-key verification → Receiver time policy
A cryptographic match does not settle signed time policy.
- 03Receiver time policy → Supported event payload
Rejected MAC/time cases do not parse JSON or open a receipt transaction.
- 04Supported event payload → Receipt and local effect
Bind the source/event receipt and local counter effect together.
- 05Receipt and local effect → Receiving evidence
Compare committed, replayed and conflicting results without adding an external effect claim.
- 06Configured-key verification → Receiving evidence
Keep a stage reason without reporting keys or MAC values.
- 07Receiver time policy → Receiving evidence
Retain a valid-MAC result separately from the refused timestamp.
Check your understanding: Does a fresh valid MAC mean the receiver should apply a new business effect?
No. The repeat and conflict cases authenticate fresh deliveries while retaining one business event identity. Check the supported payload and source/event receipt contract before another effect; authentication, freshness, idempotency and business authorization answer different questions.
3. Run ten independently reset receiving cases
Download the Python runner into a disposable folder:
python3 --version
python3 fde-webhook-receiving.py > webhook-original.json
python3 fde-webhook-receiving.py --case valid --age-seconds 301 > webhook-aged.json
python3 fde-webhook-receiving.py --case repeat
python3 fde-webhook-receiving.py --case same-event-new-bytesOn Windows, use py -3 instead of python3. There are no package installations or network requests. Each case creates and closes its own SQLite memory database. Shell redirection overwrites a file of the same name; retain original and changed reports separately. The CLI accepts no secret, URL, arbitrary body or external input file.
Predict the default outcomes:
| Case | Boundary actually tested | Actual local outcome |
|---|---|---|
valid | Original bytes, configured key and permitted send time | One receipt; four counter units |
body-changed | Units changed to seven while retaining the old MAC | MAC mismatch; no parsing or receipt transaction |
body-reserialized | Same JSON meaning, changed spacing, old MAC | MAC mismatch before receiver JSON parsing |
wrong-key | Sender uses another ephemeral key | MAC mismatch; no local effect |
changed-delivery-id | Delivery ID changed after signing | MAC mismatch because the ID is authenticated too |
stale | Authentic bytes sent 301 logical seconds earlier | Timestamp refused; no receiver JSON parsing |
future | Authentic bytes dated 31 logical seconds ahead | Timestamp refused under this fixture's policy |
repeat | New delivery ID/time, same event and exact body, new valid MAC | First applied, second replayed; still four units |
event-conflict | New valid delivery, same event ID, changed units | MAC passes, receipt conflicts; first effect retained |
same-event-new-bytes | New valid delivery and same JSON meaning, different body bytes | MAC passes, strict byte-bound receipt conflicts |
Reports retain only synthetic delivery/time/body evidence, hashes, stage results, counters and database before/after state. MAC values and keys are omitted. The actual receiver increments mac_comparisons, json_parses and receipt_transactions when those stages execute; a rejected request does not become a recorded business receipt.
4. Treat freshness and idempotency as different controls
The receiver uses fixed fictional time 1800000000, never the machine's wall clock. Its authored policy admits a signed age from minus 30 through plus 300 seconds. That is a teaching policy, not a universal webhook tolerance. Both stale and future cases have valid MACs and still fail this time check. A real owner must agree clock source, permitted skew, retry behavior and recovery evidence.
Now compare the default valid case with --age-seconds 301. Only that case's signed send time changes. Its MAC remains correct, but the receiver refuses it without parsing the payload or touching receipt state. The age control does not widen the receiver's policy. It helps distinguish a message-authentication result from a freshness decision.
Within the window, a delivery can still repeat. The repeat case independently signs delivery DEL-2 with a new send time while retaining event EVT-1 and its exact body. Both deliveries pass verification and payload checks. The receipt key is configured source plus business event ID, not MAC bytes, timestamp or delivery-attempt ID. An identical event/body replays the existing receipt without another counter increment.
A short timestamp window does not replace durable deduplication; an in-memory receipt does not establish durable recovery. If history expires or the process restarts, a real receiver needs an explicit retention/reconciliation contract. This exercise has no persistence, concurrent delivery or cross-system exactly-once guarantee.
5. Keep the receipt and local effect together
After verification, the receiver rejects duplicate JSON keys, unsupported fields/kinds, invalid event identity, Boolean/string/fractional units and out-of-contract amounts. A valid MAC authenticates supplied bytes; it does not make an unsupported payload acceptable.
For supported payloads, one actual SQLite transaction checks (source, event_id), binds the receipt to the SHA-256 hash of the exact body bytes, inserts a new receipt and updates the local counter. A repeat with identical bytes replays. A different body under the same event key conflicts and preserves the first state. The runner includes a local failure hook for focused transaction tests; it is not reachable from the signed payload or CLI.
The strict byte binding is deliberate. same-event-new-bytes has a fresh valid MAC and the same parsed JSON meaning as the first delivery, yet its receipt conflicts because spacing changed. This is not a claim that whitespace changed the business meaning. It demonstrates that the receiving contract chose byte identity rather than semantic equivalence. Real systems may need a documented canonical representation or another independently validated business payload contract; do not silently invent one after a conflict.
A receiver must not acknowledge a database failure as a completed effect. Likewise, a committed receipt here establishes only a four-unit local counter change. It is not evidence of an external side effect, downstream acceptance, repair or a provider-specific acknowledgement/retry policy.
6. Receive verification evidence as a delivery pod
- Builder: retain exact runner/runtime versions and original/changed reports. Locate the authenticated fields, actual verification stage and committed receipt/effect evidence; keep keys and MAC values out of shared records.
- Receiver: reproduce a byte-change rejection, the aged valid-MAC refusal and a freshly signed repeat. Record your own action, observed stage counts and assistance rather than inheriting the builder's result.
- Source, application and platform owners: agree the real signature scheme/SDK, source-to-key mapping, clock policy, supported event meaning, receipt identity/retention and bounded recovery. Assign key provisioning/rotation, HTTP body preservation, durable isolation and downstream reconciliation to their actual owners.
Each participant keeps an individual record:
Synthetic alias / role:
Exact runner / Python / SQLite / command:
Prediction before execution:
Observed delivery/time/body hash and evidence path:
MAC result / timestamp decision / receiver parse count:
Receipt identity / before and after local effect:
Changed send time / separate prediction and observation:
Own contribution / assistance:
Receiving action actually attempted / assistance:
Remaining boundary / next bounded test / accountable role:The exercise does not implement key rotation, secret storage, TLS, HTTP parsing, provider compatibility, end-user authorization, durable replay history or real business permission. Practice evidence does not establish qualification, independence, placement or authority to deploy. Continue with event and receiving semantics and uncertain HTTP outcomes for separate ordering, acknowledgement and reconciliation questions.
7. Defend the contract in an interview
Explain why JSON reserialization can break verification while preserving parsed meaning, and why a freshly re-signed body can still conflict with an earlier receipt. Name the exact equality contract at each boundary: bytes, supported payload, event identity or local effect.
Explain why a valid MAC does not establish freshness, new work or end-user authorization. Use the repeat case to show why signature bytes and delivery-attempt IDs make poor business receipt keys in this specific contract. Identify what a shared-secret MAC cannot prove about a particular individual.
Finally, design the next bounded real-adapter test: preserve the actual provider's raw body and signed fields, verify its supported SDK/configuration, test a duplicate and unsupported event, and retain receipt/effect evidence under the real runtime's durability and concurrency policy. Source-specific identifiers, key rotation, clock skew and acknowledgement behavior must come from that integration's contract.
Sources checked 28 October 2026: OWASP webhook security, Python 3.11 HMAC and constant-time comparison, Stripe's endpoint/signature and duplicate-event guidance, and SQLite transaction behavior. Provider guidance explains real source boundaries; this original envelope does not implement the provider's scheme. The fixtures, comparisons, diagrams and receiving exercise are original.