Skip to main content
Bank Redirects allow customers to complete payments by logging into their online banking system. Instead of entering card details, customers are securely redirected to their bank or a banking switch (e.g., FPX, Pay by Bank) to authorize the transaction. Once approved, they are returned to your site or app, and Monxa confirms the result via webhooks. This method is widely used across Asia for its security, customer familiarity, and high success rates, making it an effective alternative to card payments.

Capabilities & Constraints

  • Flow type: Redirect to bank or bank switch; result confirmed by webhook.
  • Capture model: Direct capture only (no separate auth/capture).
  • Customer experience: Bank login → approve payment → redirect/return.
  • Settlement: According to the connected provider/bank switch.
  • Refunds: Supported by many providers after capture (rules vary; see matrix below). No “reversal”/void once bank has posted the debit.
  • Retries: If abandoned/expired, create a new charge with a new Idempotency-Key.

Typical Use Cases

  • E-commerce checkout where card conversion is low
  • High-trust domestic bank rails (e.g., Malaysia FPX-style, SG pay-by-bank)
  • Desktop/mobile flows where redirect is acceptable

Supported Channels

ChannelCodeCurrencyRefundSettlementMin AmountMax Amount
Affin Bankbank_affinMYRN.AT+211000,000,000
Agro Bankbank_agroMYRN.AT+211000,000,000
Alliance Bankbank_allianceMYRN.AT+211000,000,000
Am Bankbank_amMYRN.AT+211000,000,000
BNP Bankbank_bnpMYRN.AT+211000,000,000
Bank Of Chinabank_bocMYRN.AT+211000,000,000
BSNbank_bsnMYRN.AT+211000,000,000
CIMBbank_cimbMYRN.AT+211000,000,000
Citibankbank_citiMYRN.AT+211000,000,000
Deutsche Bankbank_deutscheMYRN.AT+211000,000,000
Hong Leong Bankbank_hlbMYRN.AT+211000,000,000
HSBCbank_hsbcMYRN.AT+211000,000,000
Islam Bankbank_islamMYRN.AT+211000,000,000
Kuwait Finance Housebank_kuwaitMYRN.AT+211000,000,000
Maybankbank_maybankMYRN.AT+211000,000,000
Bank Mualamatbank_mualamatMYRN.AT+211000,000,000
OCBCbank_ocbcMYRN.AT+211000,000,000
Public Bankbank_publicMYRN.AT+211000,000,000
Bank Rakyatbank_rakyatMYRN.AT+211000,000,000
RHB Bankbank_rhbMYRN.AT+211000,000,000
Standard Charteredbank_scbMYRN.AT+211000,000,000
UOBbank_uobMYRN.AT+211000,000,000

Payment Flow

Status Lifecycle

StatusWhenMerchant Action
pendingCharge created; awaiting bank resultRedirect customer; wait for webhook
succeededBank confirmed & funds capturedFulfill order
failedBank/customer canceled or errorOffer retry / alternate method
expiredSession timed out at bank/switchCreate a new charge if needed

Field Reference

FieldTypeRequiredNotes
amountintegerMinor units.
currencystringISO-4217 (e.g., MYR, SGD, THB).
reference_idstringYour order/invoice reference.
channel_codestringe.g., bank_redirect_fpx, bank_redirect_sgpaybybank, bank_redirect_th.
channel_properties.return_urlstring(url)Where we return the customer after bank flow.
channel_properties.cancel_urlstring(url)Where to send customer if they cancel (if supported).
channel_properties.bank_codestringPreselect a bank (provider-dependent).
metadataobjectFreeform key-value pairs.
Header: Idempotency-KeystringUnique per create attempt to avoid duplicates.

Step 1: Create a Charge

Create a charge with a bank-redirect channel_code. Include a return_url (required) to bring the customer back to your site/app.
Endpoint: POST v1/charges
curl https://api.monxa.io/v1/charges \
  -H "Authorization: Bearer sk_test_***" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: chg-2025-10-09-0001" \
  -d '{
    "amount": 2500,
    "currency": "MYR",
    "reference_id": "INV-2025-1009-001",
    "channel_code": "bank_fpx",
    "channel_properties": {
      "return_url": "https://merchant.example.com/checkout/return",
      "cancel_url": "https://merchant.example.com/checkout/cancel"
    },
    "metadata": {
      "order_note": "Black Friday order #8841"
    }
  }'
{
  "id": "chg_01JBB3MZQ8Q2X",
  "object": "charge",
  "reference_id": "INV-2025-1009-001",
  "amount": 250000,
  "currency": "MYR",
  "status": "pending",
  "channel_code": "bank_fpx",
  "actions": {
    "type": "REDIRECT_CUSTOMER",
    "redirect_url": "https://bank-switch.example/session/S_abc123",
    "expires_at": "2025-10-09T05:25:00Z"
  },
  "created_at": "2025-10-09T05:20:00Z"
}
Why pending? The payment is not approved yet. Final state is delivered via webhook after the customer completes at their bank.

Step 2: Redirect the Customer

Send the customer to actions.redirect_url.
  • Web: Perform a 302 redirect or render a “Continue to your bank” button linking to redirect_url.
  • Mobile: Open in external browser or in-app webview. Handle the return_url to resume your flow.

Step 3: Customer Approves at Bank

The customer authenticates with their bank and approves the payment. The bank/switch posts the result back to Monxa.
  • If successful → funds are captured and status becomes succeeded.
  • If rejected/canceled → status becomes failed.
  • If not completed in time → status becomes expired.
Always verify by fetching the charge: GET /v1/charges/

Step 4: Handle Webhooks & Update Payment Status

Always verify the event by fetching the charge (GET /v1/charges/) before updating your system.
{
	"id": "evt_01JBB3S1RZK8N",
  	"type": "charge.succeeded",
  	"data": {
    	"id": "chg_01JBB3MZQ8Q2X",
    	"reference_id": "INV-2025-1009-001",
    	"amount": 250000,
    	"currency": "MYR",
    	"status": "succeeded",
    	"channel_code": "bank_redirect_fpx",
    	"captured_at": "2025-10-09T05:21:12Z"
	},
  	"created_at": "2025-10-09T05:21:13Z"
}
{
	"id": "evt_01JBB3FAIL001",
  	"type": "charge.failed",
  	"data": {
    	"id": "chg_01JBB3MZQ8Q2X",
    	"reference_id": "INV-2025-1009-001",
    	"status": "failed",
    	"failure_code": "bank_declined",
    	"failure_message": "Customer canceled or bank declined"
  	},
	"created_at": "2025-10-09T05:22:40Z"
}

Implementation Tips

  • UI: Always show a “Continue to your bank” CTA and a fallback “Try another method.”
  • Timeouts: Respect actions.expires_at; show session countdowns to reduce abandonment.
  • Mobile deep-linking: Some providers return app deep links; handle gracefully with browser fallback.
  • Reconciliation: Store charge.id, reference_id, bank bank_code (if present), and webhook timestamps.

Error Handling

ErrorMeaningHow to Resolve
400 invalid_channel_propertiesMissing/invalid return_url or bank_codeProvide required fields; validate URLs
400 unsupported_channelChannel not enabled or not available in corridorEnable channel or choose supported one
402 bank_declinedBank rejected the paymentOffer retry/alternative method
409 duplicate_referencereference_id uniqueness conflict (if enforced)Use a unique reference or update config
422 amount_mismatchAmount not accepted by providerEnsure correct minor units and limits