End-to-End Testing for Payments and Webhooks
A checkout page that redirects to a "thank you" screen proves almost nothing. The part that matters, recording that an order was paid, granting access, sending a receipt, usually happens in a webhook handler that runs out of sight, triggered by the payment provider rather than the user. It is also the part AI-generated code most often gets subtly wrong. This guide uses Stripe as the worked example, since its test tooling is widely used, but the same reasoning applies to other providers.
Understand the flow you are testing
In a typical integration there are two independent paths:
- The user path. Your server creates a Checkout Session or PaymentIntent, the user pays, and the browser is redirected to a success URL.
- The event path. The provider sends an HTTPS POST to your webhook endpoint describing what happened, for example
checkout.session.completedorpayment_intent.succeeded.
Only the second path is reliable for fulfilment. The user may close the tab before the redirect, or reload the success page five times. A common flaw in generated code is marking the order as paid when the success page loads. Your first test is simply to read the code and find where the order status changes. If it is in the success page handler, that is the first fix.
Set up a local test loop
You need events from the provider to reach your development machine. The Stripe CLI does this by forwarding test-mode events:
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripeThe listen command prints a webhook signing secret starting with whsec_. Put it in your local environment as the webhook secret, then, in another terminal, fire synthetic events:
stripe trigger checkout.session.completed
stripe trigger payment_intent.payment_failedFor a full end-to-end run, go through your real checkout in test mode using Stripe's documented test card 4242 4242 4242 4242, any future expiry date and any CVC. The documentation lists other test numbers that simulate declines and authentication challenges; use them too.
The test cases that catch real bugs
Work through these deliberately and record the expected result for each before you run it.
- Signature verification. Send a request to the endpoint without a valid signature, for example with plain
curl -X POST. It must be rejected. The handler should verify using the raw request body; frameworks that parse JSON before your code sees it are a classic reason verification fails, and a classic reason someone "fixes" it by removing the check. - Duplicate delivery. Providers may deliver the same event more than once. Resend an event from the dashboard or replay it, and confirm the order is not fulfilled twice. The usual fix is storing processed event IDs and skipping repeats, or making the database update idempotent.
- Out-of-order events. Events are not guaranteed to arrive in sequence. Make sure a late "created" event cannot overwrite a "paid" status.
- Handler failure. Make your handler throw deliberately. The provider should record a failed delivery and retry later; your code should not leave a half-updated order. Return a success status only after the work is committed.
- Slow processing. If fulfilment calls other services, keep the handler quick by acknowledging the event and doing heavy work in a background job, so the provider does not time out and retry.
- Failed and refunded payments. Trigger a decline and a refund, and confirm access is not granted, or is revoked, accordingly.
- Mode mismatch. Check that test-mode keys and live-mode keys are never mixed, and that the live webhook endpoint has its own signing secret.
Moving to production
When you switch to live mode, the webhook endpoint must be registered again for live events, with the production URL and a new signing secret stored in your production environment variables. Make one small real purchase yourself, confirm the order updated, and refund it. Then check the provider's dashboard for webhook delivery errors over the first few days of real traffic.
When to get help
If the integration involves subscriptions, proration, multiple currencies or marketplace-style payouts, the number of event types grows quickly and a developer who has built such flows before is worth the cost. When you hire through a freelance marketplace like Workli, note that the platform itself does not process payments or hold funds; you agree directly with the freelancer how milestones are paid. That is separate from the payment system you are building, but worth knowing when you set up the engagement. Hand the developer your list of test cases; it doubles as a specification and as acceptance criteria.
Payments feel finished the moment the first test charge succeeds, which is exactly why the webhook path deserves a deliberate test plan. Run the cases above once before launch and again after any change to the checkout, and you will catch the double fulfilments and silent failures before your customers do.
Comments 0
Comments are reviewed before they appear. Oldest first.
No comments yet. Spotted a typo, a better flag, or a step that failed for you? Say so below.