Worked verification flow
A demo body contains event ID evt_demo_1 and a paid status. The sender signs the timestamp and exact body bytes. The receiver checks timestamp age and HMAC before JSON parsing. If one space is added, the body no longer matches and the test rejects it. If the same valid event is delivered twice, both signatures may be valid; the database unique key must allow only one ledger effect. The downloadable example covers signature checks, while the database deduplication step is described here for implementation. No live provider secret or customer payload is included.
Verify before you trust
Capture the raw body bytes before a JSON parser changes spacing or character encoding. Look up the signing secret for the endpoint. Apply the provider’s documented timestamp and header format, then compare the computed signature in constant time.
Checks to run
- Reject a changed body, missing signature, wrong secret, and stale timestamp.
- Compare signatures with a timing-safe function. Use the provider’s exact signed payload format.
- Deduplicate the verified event ID inside the transaction that creates the ledger entry.
Time and byte inputs
The example takes whole Unix seconds for both the signed timestamp and current time. Use Math.floor(Date.now() / 1000) for the clock. It accepts a 300-second gap in either direction, including the boundary. Missing values, NaN, fractions, and empty secrets fail. Pass a Uint8Array or Buffer to preserve the request bytes; a string works only when it keeps the exact encoding. The clock rule is for this demo. Apply your provider’s rule in a real handler.
Failure matrix
The test file covers a changed raw body, wrong secret, expired timestamp, and malformed signature. A valid redelivery is also expected in production: signature validity answers who sent the message, while event deduplication answers whether its money effect has already run.
The example uses HMAC-SHA256 over a demo payload. It is not a drop-in Stripe or Paystack handler. Match the actual provider header, secret rotation, signed bytes, and timestamp rule before accepting live events.
Test bytes, retries, and state
Send the same valid webhook body twice with its real signature. Signature verification should pass both deliveries, while the ledger effect should appear once. Next, add one space to the raw body without recomputing the signature: verification must fail. Then deliver a success event before a pending event and confirm the final payment does not move backward. Keep the raw body only in a safe test fixture; production logs should avoid secrets and personal data. Stripe warns that event order is not guaranteed. That behavior makes signature checks and state checks separate tests.
Deduplicate the payment effect as well as the event
Create two valid provider messages with different event IDs that both claim success for the same payment P1. The event inbox can hold both; the business-effect key must still allow one posting for P1 and that effect type. Choose that key from the provider contract rather than assuming each event ID means a new payment. In a separate run, crash after saving a verified inbox row but before queue delivery. Retry the same event and process the saved row. Return a success acknowledgment only after durable acceptance under the endpoint’s rule. Signature validity does not show whether the money effect already ran or whether the event state is current.
Primary sources
Download verifier source, Bun tests, input rules, MIT license.
Next step
Run the negative signature tests, then adapt the signed bytes and header rule from your own provider documentation. Read the related guide. For a review of your own system, request a security review.