Skip to content
jagaweb.Book the Review
WooCommerce & Malaysian Payments

The Payment Gateway Integration Checklist: What to Verify Before You Go Live

11 min readBy JagaWeb

What to verify in a payment gateway integration: webhook idempotency, browser drop-off, reconciliation, refunds and PCI scope.

"It worked when I tested it" is not the same as "it works"

A payment gateway integration can pass a developer's own manual test, enter a test card, click pay, see the success page, and still fail in production, because almost none of the failure modes that matter show up when one person clicks "pay" once, on a fast connection, without closing the tab. What actually goes wrong is what happens when a network call times out, when the same notification arrives twice, when a customer closes their laptop mid-payment, or when someone in finance tries to match what the site says was paid against what the gateway actually settled. What follows is a checklist of what to verify before, and after, a gateway goes live — written generically enough for whichever gateway a Malaysian merchant chooses, because the underlying failure modes repeat across providers even though the API details differ.

Sandbox and live are not just a toggle

Every gateway separates test (sandbox) credentials from live credentials, and the first thing worth verifying before launch is that the two haven't been mixed up: no live secret key left in a staging environment where a test build could process a real card, and no sandbox key still wired into production after go-live, which usually fails loudly but occasionally fails quietly if a cached config keeps the old value alive. Secret keys belong in environment variables, never committed into source code or hardcoded into a config file that ends up in version control. A leaked live secret key is a direct path to unauthorised charges appearing in your account, and rotating it after any suspected leak, not just after launch, should be a defined, rehearsed step.

Webhooks: why the same event can legitimately arrive twice

A webhook is the gateway calling your server directly, independently of the customer's browser, to tell you something happened: a payment succeeded, a refund was issued, a dispute was opened. The critical thing to design around is that every major provider's webhook system is built on "at least once" delivery, not "exactly once." Stripe's own documentation states this outright: failed webhook deliveries are retried with exponential backoff for up to three days in live mode, and events aren't guaranteed to arrive in the order they were generated, so a handler should track event IDs to detect duplicates rather than assume each event arrives exactly once. Malaysian gateways document the same reality. Curlec (Razorpay)'s documentation states plainly that "there could be scenarios where your endpoint might receive the same webhook event multiple times. This is an expected behaviour," and instructs merchants to deduplicate using the unique event ID header sent with each webhook. Billplz's own integration guide describes a related issue: its callback (server-to-server) and redirect (browser) notifications fire in no fixed order relative to each other, and its documentation states directly that "your system must handle both independently and prevent duplicate updates."

Concretely, duplicates happen for mundane reasons: your server received the webhook and started processing it, but a network hiccup or a deploy restarting the server mid-request meant it never sent back a success response, so the gateway, with no way to know the event was handled, retries; or someone manually resends an event from the dashboard while debugging something unrelated. If a webhook handler isn't idempotent, if it can't recognise "I've already processed this exact event" and skip redoing the work, the second delivery of the same "payment succeeded" event can mark an already-fulfilled order as paid a second time, trigger a duplicate confirmation email, decrement stock twice, or apply a loyalty credit twice. The fix is structural, not clever: store the event ID the first time you see it, and check for that ID before acting on any webhook, so a repeat delivery is recognised and safely ignored instead of reprocessed.

What actually happens when a customer closes the browser mid-payment

This is where relying only on the customer's browser redirect to decide whether an order is paid falls apart. If a customer completes payment on the gateway's own page, their bank or card issuer confirms the charge, but closes the tab before being redirected back to your site, the payment can still be genuinely successful even though your site never saw a "thank you" page fire. Depending purely on that redirect to mark an order paid means a real, successful payment can sit unrecorded in your own system indefinitely. This is exactly why the webhook, a separate, server-to-server notification that doesn't depend on the browser still being open, is the reliable source of truth for whether an order was actually paid — the redirect is a convenience for the customer's experience, not the trigger that decides whether an order gets fulfilled.

Reconciliation: your own records against the gateway's own dashboard

However solid the webhook handling is, it's worth periodically checking your own order records against the gateway's dashboard or settlement report directly, because the two can drift apart for reasons that have nothing to do with a coding bug: a webhook endpoint briefly down during a deploy, a firewall change that silently blocked incoming requests, or a retry sequence that eventually gave up. Two mismatches specifically are worth watching for: an order marked "paid" in your own system with no matching transaction in the gateway, worth investigating as a bug or a fraud attempt, and a successful transaction in the gateway with no matching order in your system — usually a missed webhook, and a silent revenue or fulfilment gap if it goes unnoticed.

Refunds are a separate flow, not a reversed charge

Issuing a refund through the gateway's dashboard or API doesn't automatically update your own order status, inventory, or accounting records; that update has to be handled explicitly, typically by listening for the gateway's own refund event the same way a payment success event is handled. It's also worth confirming, for whichever gateway and payment method is in use, how long a refund takes to reach the customer in practice — it is very often not instant even when the original payment was, and setting that expectation up front heads off support tickets asking where the money went.

Currency and rounding

If a store calculates a total in one place, a cart total computed in your own code, and the gateway calculates or displays it in another, its checkout page, or a converted currency, small rounding differences between the two, a sen here or there from floating-point arithmetic, or a conversion applied at a slightly different rate or moment, can leave your order total and the amount actually charged a cent or two apart. That's rarely catastrophic alone, but it's exactly the kind of small, silent mismatch that makes reconciliation harder and quietly erodes trust in whether "paid" means what your system says it means. Where a store supports more than one currency, it's worth being explicit about which currency the customer is actually charged in, and at what point the exchange rate is locked, rather than leaving it implicit.

PCI scope: hosted checkout versus handling card fields yourself

How much Payment Card Industry Data Security Standard (PCI DSS) compliance burden a merchant carries depends heavily on how card data physically moves through the integration, not on which gateway is chosen. The PCI Security Standards Council's own FAQ sets out the dividing line: a merchant is eligible for the simplest self-assessment questionnaire, SAQ A, only where the entirety of the payment pages delivered to the customer's browser originates directly from a PCI DSS-validated third party — a fully hosted checkout page, or an iframe where no merchant-controlled script runs on that page. The moment any part of the payment form is built or scripted by the merchant's own site, a card field rendered directly on your page, even one that posts straight through to the processor, that eligibility changes and a more demanding questionnaire applies, because the Council treats a merchant-controlled script on a payment page as a materially different risk from a fully outsourced one. In practice, this is a strong, concrete reason to use a gateway's hosted checkout or a properly isolated iframe rather than building custom card fields, unless there's a specific, well-understood reason to take on the larger compliance scope.

Before you flip the switch to live

None of this is exotic; it's the ordinary difference between an integration tested once by a developer and one checked against how payments actually misbehave in production: duplicate webhooks, abandoned sessions, drift between your records and the gateway's, and refunds and currency that don't reconcile automatically. If you'd like an independent, technical check of a payment integration before or after it goes live, that sits within the scope of JagaWeb's Essential System Review (RM1,500, reduced to RM999 until 16 September 2026, excluding SST) — one option among several; if what's actually needed is a full custom integration built from scratch, a Fixed-Scope Project (from RM30,000) is the more accurate starting point.

PROTECT YOUR ASSETS

Ready to verify who owns your website?

Replace uncertainty with a decision-ready ownership and access report. The fixed Ownership & Access Review is RM1,500 before SST and includes a 30-day action plan.

WhatsApp