Malaysia Payment Gateway logoMPG
← Learn

The safe integration flow

Every gateway differs in API, but the secure shape is the same. Follow this order and a payment can't be spoofed.

┌────────────────────────────────────────────────────┐
│ CUSTOMER                                           │
│ Clicks "Pay" on your site                          │
└────────────────────────────────────────────────────┘
                          │
                          │   create order
                          ▼
┌────────────────────────────────────────────────────┐
│ YOUR BACKEND                                       │
│ Writes a PENDING order (unique reference)          │
└────────────────────────────────────────────────────┘
                          │
                          │   request checkout
                          ▼
┌────────────────────────────────────────────────────┐
│ GATEWAY                                            │
│ Returns a hosted checkout URL                      │
└────────────────────────────────────────────────────┘
                          │
                          │   redirect
                          ▼
┌────────────────────────────────────────────────────┐
│ CUSTOMER PAYS                                      │
│ FPX / card / e-wallet / DuitNow QR                 │
└────────────────────────────────────────────────────┘
                          │
                          │   bank authorises
                          ▼
┌────────────────────────────────────────────────────┐
│ GATEWAY                                            │
│ Sends a SIGNED webhook to your backend             │
└────────────────────────────────────────────────────┘
                          │
                          │   webhook
                          ▼
╔════════════════════════════════════════════════════╗
║ YOUR BACKEND  (the trust gate)                     ║
║ 1. verify the signature                            ║
║ 2. re-fetch the real state from the gateway        ║
║ 3. match reference + amount + status               ║
║ 4. mark paid ONCE  (idempotent)                    ║
╚════════════════════════════════════════════════════╝
                          │
                          │   confirmed
                          ▼
┌────────────────────────────────────────────────────┐
│ UNLOCK ACCESS                                      │
│ Show success to the customer                       │
└────────────────────────────────────────────────────┘
How one payment flows. The money is only trusted at the double-lined gate, after the gateway's signed webhook is verified and re-fetched. Never from the browser redirect.

Step by step

  1. 1

    Create a local pending payment

    Before sending the customer anywhere, write your own order/payment row with status pending and a unique reference. This is the record you'll later confirm; never rely on the gateway's memory alone.

  2. 2

    Redirect to checkout (or open the hosted page)

    Hand off to the gateway's hosted checkout / payment link with your reference and amount. Let the gateway handle the card/FPX/e-wallet UI, so you collect nothing sensitive.

  3. 3

    Verify the webhook signature

    When the gateway calls your backend, verify the signature/checksum (usually HMAC with your secret) before reading anything. An unverified callback is untrusted input, so reject it.

  4. 4

    Re-fetch the state from the provider

    Don't trust the webhook body's status at face value. Call the gateway's API to read the authoritative payment state for that reference.

  5. 5

    Match provider + reference + amount + currency + status

    Confirm the payment maps to your pending record on every field. A mismatch (wrong amount, wrong currency, unknown reference) means do not fulfil.

  6. 6

    Mark paid, idempotently

    Flip your record to paid and unlock access. If the same event arrives again (gateways retry), detect it by reference and do nothing the second time. No double-fulfilment, no double-counting.

The two rules that matter most

Tests to write