The notification is a request, not a receipt
A payment notification reaches an internet facing address that anyone can send a request to. A JSON field saying paid is therefore only a claim until the shop has verified the sender and interpreted the event in its own order context. This distinction matters when fulfilment is automated: a forged request could otherwise move an unpaid order into a packing queue. The first business decision is to identify the exact event source, endpoint, and order transition that the team intends to trust. Record that mapping before a developer adds code, so staff know which confirmation is authoritative.
Stripe documents a signed webhook header and an endpoint signing secret. Its guidance says verification uses the raw request body, the Stripe-Signature header, and the secret associated with that endpoint. That is a concrete example, not a claim that every payment provider signs events in the same way. If another gateway is used, read its own current verification instructions and test its sandbox against production configuration. Do not substitute a familiar looking event ID, a public URL, or a successfully parsed JSON object for the documented verification step.
Write the acceptance rule in ordinary language: only a request that passes the provider verification step may enter the payment event handler. An invalid request receives an unsuccessful response and causes no order change, message to the customer, stock reservation, or accounting entry. Make this rule visible in code review and in a test case. It is also useful to distinguish an invalid signature from a valid event that names an unknown order; both need investigation, but only the latter has passed the authenticity boundary.
Preserve the exact bytes used for verification
A common implementation failure happens before the verification function runs. A web framework may parse JSON, reorder keys, normalize whitespace, or transform the encoding. Stripe explicitly warns that any change to the raw body can make signature verification fail. Give the webhook route access to the original body and follow the provider library instructions for that framework. Parsing for business fields should happen only after authenticity has been established. If a reverse proxy or serverless gateway transforms the request, inspect its documented mapping too, because application code cannot recover an original body that an upstream layer discarded.
The signing secret is specific to the endpoint. Stripe says that a secret printed by its local CLI listener is different from a Dashboard managed endpoint secret. A development endpoint can therefore pass locally while the live route fails every delivery if those values are mixed. Maintain a small configuration table showing environment, destination URL, event format, secret owner, and rotation procedure without recording the secret itself in the article or test report. Limit who can reveal it, store it through the application secret mechanism, and test that a missing or incorrect value fails closed.
For a worked rehearsal, take one provider generated test event and capture the exact bytes at the application boundary. Verify it once unchanged, then alter a harmless looking space or one amount digit and verify again. The unchanged event should pass and each altered version should fail before any order write. Repeat through the deployed proxy path rather than only calling a local function. This does not prove that the checkout is correct; it proves the specific boundary that protects the order handler from unsigned or changed payloads.
Treat a valid signature as time bounded evidence
Authenticity is not the same as freshness. Stripe signs a timestamp with the payload and its libraries use a default tolerance of five minutes; its documentation warns that setting tolerance to zero disables the recency check. The server clock therefore needs to be accurate. A shop should retain the library default unless it has a documented reason and has tested the consequence of changing it. If legitimate deliveries fail due to clock skew, fix time synchronization and investigate the deployment before widening the window. A wider acceptance window gives a captured valid request more time to be replayed.
The Stripe documentation also explains that a retry of the same event gets a new signature and timestamp. That means a valid recent signature is proof about one delivery attempt, not a promise that the event has never appeared before. Keep the authenticity check separate from the business rule that prevents an event from changing an order twice. The existing event recovery guide addresses repeated, delayed, and missed delivery; the present decision is whether any given HTTP request may be trusted enough to reach that logic at all.
A useful test matrix includes a valid new delivery, no signature header, a signature from the wrong endpoint secret, an altered body, a stale signed request, and a server with deliberately skewed time. Write the expected response and expected absence of side effects for each case. Then test a legitimate retry with its newly issued signature to confirm the authenticity layer does not reject normal provider behavior. Use provider tools or fixtures for realistic headers; do not fabricate a successful signature by editing a captured header string.
Verify the order meaning after the trust check
After signature verification, inspect the event type and the identifiers against the business record. A valid event could concern a different payment, a cancelled order, a test mode object, or a status that does not mean funds are ready for fulfilment. Build an explicit transition table: current order state, provider event and object status, expected payment reference, allowed next state, and staff exception. Only the combinations approved by the business should trigger packing. The table prevents a developer from treating every signed notification as permission to ship.
Consider an illustrative order for one item. The payment page returns the customer to a thank you screen, but the payment event has not arrived. Staff see a screenshot from the buyer. The correct next step is to leave the order pending and use the provider record and a verified event or supported API check according to the shop policy. A screenshot and a browser redirect are customer experience signals, not authenticated payment evidence. When the verified event arrives, compare its payment reference to the pending order before moving the order forward.
Keep an audit record that is useful without copying secrets or entire sensitive payloads into routine logs. Record receipt time, provider event identifier, verification outcome, endpoint environment, linked order, decision, and a safe error category. Restrict access and retention according to the business purpose. During an incident, this trail lets staff distinguish a bad signature from a valid but unrecognized order or a business rule rejection. It also makes it possible to explain why an order stayed pending without claiming that the payment provider failed.
Release with a small, observable acceptance plan
Before enabling automatic fulfilment, run the full path in a nonproduction environment: provider sends a test event, proxy passes it unchanged, verifier accepts it, and an allowed order transition occurs once. Run the negative cases through the same path. Review which HTTP responses cause a provider retry and keep slow downstream work out of the immediate response where the provider requires a quick acknowledgement. This is an operational rehearsal, not an invitation to process a failed verification in the background.
For the first live release, assign an owner to watch verification failures, valid events without matching orders, and orders still pending after a customer returns from checkout. Set an escalation path to inspect provider delivery records and the order ledger. A sudden failure spike after deployment often points to changed middleware, environment configuration, or a rotated endpoint secret. Investigate those specific causes before asking staff to bypass the verifier. The temporary business response can be manual confirmation and delayed fulfilment, with a clear customer explanation.
The final acceptance question is precise: can an unauthenticated or altered request change an order, even if it looks like a plausible payment event? If the answer is yes, hold automatic action and repair the trust boundary. If the answer is no, confirm that a genuine signed event still reaches the correct order transition and that exceptions remain visible to staff. This gives an SME a concrete release decision without assuming a provider logo, a green status light, or a single successful test is enough.
A secret rotation needs its own rehearsal. Record which endpoint receives each environment, who can change its secret, how both old and new signatures will be accepted during the provider supported overlap, and when the old secret will be removed. Stripe documents that an endpoint can temporarily have multiple active secrets during a roll. After the change, send a fresh provider test event and confirm that an event signed with an unrelated secret still fails. If verification suddenly fails, restore a valid configuration rather than switching verification off to keep orders moving.
A final handover should include who owns a verification alarm outside office hours. If a gateway starts sending events that fail the check, the responder needs the endpoint name, environment, deployment change, and a safe way to compare delivery records without exposing secrets. Decide how pending orders are labelled and when customers receive an update. This turns a cryptographic failure into a controlled business exception. It also gives the team a reason to preserve the verifier even when the queue grows.
Before you proceed
- The raw body reaches the verifier unchanged.
- Wrong, missing, altered and stale signatures cause no order write.
- A valid event matches an allowed order transition before fulfilment.


