What is a payment webhook?
A payment webhook is a message a provider sends to your server when something happens, such as a payment succeeding. Your application uses that event to update its own records. Payment acceptance and delivery of the purchased item are different steps: receiving money does not automatically create a download, shipment or account entitlement.
This guide uses Stripe Checkout as a concrete example and a fictional digital workbook as the product. It is a design and testing walkthrough, not drop-in production code. Other providers use different events, signature schemes and payment states; use their own documentation when implementing.
Why a success page cannot be the only trigger
Stripe’s fulfillment guide explains that a paying customer may never reach your landing page. A browser can close or lose connectivity after payment. Webhooks let your server learn about the result independently of that visit.
In Stripe Checkout, completing a session is not always the same as a delayed payment succeeding. The fulfillment flow checks payment status, and delayed methods can produce a later async-payment success event. A no-payment-required order is another possible case; define whether your product intentionally supports it.
Treat the browser as a status display. It can ask your server whether the order is ready, but a query parameter saying success must not grant access. Otherwise the customer’s browser is effectively deciding whether the bill was paid.
Verify the message before trusting its fields
For Stripe, verify the signature against the unchanged raw request body using the endpoint’s signing secret and an official library. Parsing and rewriting JSON before verification can change the bytes. Keep test and live endpoint secrets separate.
OWASP’s webhook guidance also covers freshness checks, request limits and safe failure handling. A valid signature authenticates a delivery; it does not tell your application that the referenced order belongs to the correct customer or has the correct price.
In our workbook example, the server created order W-104 for customer C-8, priced at 1,200 US cents. Before granting access, compare the verified provider object with those saved facts: environment, account, linked order, amount and currency. A signed event for a different order is still the wrong event for W-104.
A duplicate delivery is not a second purchase
Stripe documents retries, duplicate events and delivery without a guaranteed order. Track event IDs, but also make the business operation safe to repeat: two notifications can refer to the same underlying payment.
Consider this original sequence. At 10:00, the server receives event E-71 for W-104 and creates one workbook entitlement. At 10:01, the same event arrives again. At 10:02, the customer refreshes the status page. There is still one purchase for $12, not three purchases for $36.
Use two durable records in this design: receipt of the event and completion of the entitlement for the order. Put a uniqueness constraint on the entitlement’s order identifier. Two workers checking an ordinary Boolean at the same time can both see false; a database-enforced constraint prevents both from creating the same entitlement.
Do not mark delivery complete before the work is safely recorded. If a worker crashes between claiming the event and granting access, it must be possible to retry. A durable queue plus a transaction or transactional outbox can coordinate these stages. External actions such as shipping still need their own repeat-safe mechanism and reconciliation.
API idempotency and webhook deduplication are different
Idempotency means a retry has the intended effect of the original operation rather than creating an extra one. An idempotency key accompanies your outgoing API request; webhook deduplication handles incoming notifications. Neither automatically replaces the other.
Stripe distinguishes API v1 and v2 retry behavior. For v1, a key can return a saved result, including a server error; keys can be pruned after at least 24 hours. Do not treat a provider’s temporary request cache as a permanent order ledger. Consult the version-specific rules before retrying failed work.
For W-104, keep one stable identifier for the intended purchase attempt. A connection timeout does not justify inventing a second order. If the user intentionally buys another workbook, create a distinct order and request key. If the amount changes, reconcile or replace the original attempt deliberately instead of silently reusing its identity with different parameters.
Practice: replay this order without delivering twice
Use a provider sandbox and fictional customer records. First create one $12 test order. Record its expected currency, customer and provider reference. Then deliver the same successful notification twice, close the browser before its success page appears, and refresh the order page afterward.
The expected answer is one fulfilled order and one entitlement. The stored event record can show multiple delivery attempts, but each must converge on the same business result. Confirm this by inspecting records, not by counting how many times the webhook URL returned a successful HTTP status.
Next simulate a crash after the event is durably accepted but before the worker finishes. The pending job should recover. If the database or queue cannot accept the event, do not acknowledge it as safely accepted. Returning success too early can leave the provider believing no retry is needed.
PROMPT TO ADAPT
Draft a sandbox-only test plan for a digital workbook checkout. The fictional order W-104 costs 1,200 US cents and belongs to customer C-8. Cover duplicate and out-of-order events, invalid signatures, amount mismatches and a worker crash. For each test, state the expected database records and entitlement count. Identify which checks require provider documentation. Do not use live keys, charge a card or present pseudocode as production-ready.
- Valid paid order: exactly one entitlement appears.
- Repeated event and simultaneous workers: the entitlement stays unique.
- Invalid signature: no paid status or entitlement is created.
- Wrong currency, amount or customer mapping: hold for investigation.
- Delayed payment: no paid entitlement until the required status is confirmed.
- Older event after a newer state: no blind reversal to an obsolete status.
- Worker crash: durable pending work can resume without duplicate delivery.
Connect this to an AI-built app or x402 service
If AI generated your checkout code, ask it to explain the path from payment evidence to the single business action. Look for durable state, validation and recovery rather than assuming a successful demo proves reliability.
Our x402 integration guide discusses paid API requests. This page covers asynchronous checkout notifications; x402 does not become a Stripe webhook simply because both involve payments. The shared engineering question is what happens when a request repeats or a process stops halfway through.
Before launch, write an operational answer to three questions: how will you find an accepted payment without fulfillment, how will you safely retry it, and how will you explain the status to the customer? Keep refund and dispute handling as explicit additional workflows. A payment marked successful today is not a promise that its business lifecycle is over.
Sources & further reading
- Stripe: webhook delivery, signatures and duplicates
- Stripe: Checkout fulfillment
- Stripe API: idempotent requests and version differences
- OWASP: webhook security
Educational content. Examples are illustrative. Protocols, products and rules can change; consult the linked documentation for current details.

