Accept bank transfer payments
You can accept payments in three simple steps:
- Initialize a Payment Intent
- Generate Transfer Instructions
- Query the Payment Status
Step 1. Initialize a Payment Intent
Create a Payment Intent using the Create a Payment Intent API API.
Example request:
Shell1curl --request POST \2--url 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/create' \3--header 'Content-Type: application/json' \4--header 'Authorization: Bearer <your_bearer_token>' \5--data-raw '{6 "request_id": "ed11e38a-7234-11ea-aa94-7fd44ffd1b89",7 "customer_id": "cus_hkdm564srha6ejk55lt",8 "amount": 100,9 "currency": "USD",10 "order": {11 "products": [12 {13 "name": "apple",14 "quantity": 5,15 "unit_price": 20,16 }17 ],18 "shipping": {19 "first_name": "John",20 "last_name": "Shooper",21 "address": {22 "country_code": "US",23 "city": "New York",24 "state": "NY",25 "postcode": "10001",26 "street": "16 Sandilands Road"27 }28 }29 },30 "merchant_order_id": "85d7b0e0-7235-11ea-862e-9f6aa1adfca6",31 "return_url": "https://www.airwallex.com"32}'
Step 2. Generate Transfer Instructions
Confirm the Payment Intent with Bank transfer, using the Confirm a Payment Intent API API.
JSON1{2 "request_id": "5460fc4a-cb3d-417a-a2e9-697f9e54026e",3 "payment_method": {4 "type": "bank_transfer",5 "bank_transfer": {6 "shopper_email": "[email protected]"7 }8 }9}
If you are using the dedicated account per customer model, we will send below error message if a customer_id is not provided.
JSON1{2 "code": "validation_error",3 "message": "customer_id is required for the dedicated account per customer model.",4 "trace_id": "7b944a3b3a0afcc61529f33ac76ad1a0",5 "details": {}6}
The transfer instructions provided will be tailored to the currency of the payment intent. We provide 2 options:
- Redirect your customer to Airwallex transfer instruction page by using
next_action.url. - Display the transfer instruction in your customized format, using the details in
next_action.data.transfer_instructions.
USD
JSON1{2 // ... other fields omitted.3 "next_action": {4 "type": "redirect",5 "method": "GET",6 "url": "https://api-staging.airwallex.com/pa/redirect/sg/sgst9qnk8h4wqqbt2ar_q5j0wu?checksum=1537c94a4978",7 "data": {8 "transfer_instructions": {9 "account_name": "Tromp, Littel and Luettgen",10 "account_number": "8453687204",11 "bank_name": "Community Federal Savings Bank",12 "reference": "QCNLQC",13 "routing_number": "026073150"14 }15 }16 }17}
EUR
JSON1{2 // ... other fields omitted.3 "next_action": {4 "type": "redirect",5 "method": "GET",6 "url": "https://api-staging.airwallex.com/pa/redirect/sg/sgstdr286h4xzn9nydl_n41u6d?checksum=6631bfa9ca08",7 "data": {8 "transfer_instructions": {9 "account_name": "Tromp, Littel and Luettgen",10 "bank_name": "Airwallex (Netherlands) B.V.",11 "iban": "NL71AINH0034616122",12 "reference": "4L6R9C",13 "swift_code": "AINHNL22"14 }15 }16 }17}
GBP
JSON1{2 // ... other fields omitted.3 "next_action": {4 "type": "redirect",5 "method": "GET",6 "url": "https://api-staging.airwallex.com/pa/redirect/sg/sgsttg5tbh4xzpux89l_po4l7q?checksum=158a4fe0fcc2",7 "data": {8 "transfer_instructions": {9 "account_name": "Tromp, Littel and Luettgen",10 "account_number": "04411119",11 "bank_name": "Modulr FS Limited",12 "reference": "D5NCUC",13 "sort_code": "000000"14 }15 }16 }17}
SGD
JSON1{2 // ... other fields omitted.3 "next_action": {4 "type": "redirect",5 "method": "GET",6 "url": "https://api-staging.airwallex.com/pa/redirect/sg/sgsttg5tbh4xzpux89l_po4l7q?checksum=158a4fe0fcc2",7 "data": {8 "transfer_instructions": {9 "account_name": "Airwallex (Singapore) Pte Ltd",10 "account_number": "885388629",11 "bank_name": "DBS Bank Ltd",12 "reference": "4ALTYC",13 "swift_code": "DBSSSGSG"14 }15 }16 }17}
AUD
JSON1{2 // ... other fields omitted.3 "next_action": {4 "type": "redirect",5 "method": "GET",6 "url": "https://api-staging.airwallex.com/pa/redirect/sg/sgsttg5tbh4xzpux89l_po4l7q?checksum=158a4fe0fcc2",7 "data": {8 "transfer_instructions": {9 "account_name": "Tromp, Littel and Luettgen",10 "account_number": "12345678",11 "bank_name": "Australia and New Zealand Banking Group Limited",12 "reference": "4ALTYC",13 "bsb_code": "013943"14 }15 }16 }17}
Handling virtual account availability issues
Some currencies, especially SGD, have a limit on the number of virtual bank account numbers that can be created within a short period. If this limit is reached, the system will return the following error code.
To prevent disruptions, please contact your Account Manager in advance if you anticipate high transaction volumes to request a limit increase.
JSON1{2 "code": "provider_unavailable",3 "message": "The payment provider is temporarily unavailable. Please try again later.",4 "trace_id": "f75a5ac8986c7b6dbd33eb29b2683565",5 "details": {6 "original_response_code": "account_limit_reached"7 }8}
Step 3. Query the Payment Status
To get the payment result, we recommend polling the status of the Payment Intent via the Retrieve a Payment Intent API API.
In addition, Airwallex will notify you of the payment result asynchronously via webhooks. Please refer to the webhook documentation to set up your webhook accordingly.
Sample webhook
JSON1{2 "id":"evt_sgstrwnsqh50bjcpqce_j43hcy",3 "name":"payment_attempt.capture_requested",4 "account_id":"acct_mqOgoGfwMYWniICB7RXLYg",5 "data":{6 "object":{7 // other fields omitted ...8 "status":"CAPTURE_REQUESTED",9 "amount":150,10 "captured_amount":150,11 "currency":"SGD",12 "id":"att_sgstspr5qh50bj6g4ej_j43hcy",13 "merchant_order_id":"e22ddc16-8154-4b81-91e2-049fe068adab",14 "payment_intent_id":"int_sgstrwnsqh50bj43hcy",15 "payment_method":{16 "bank_transfer":{17 "shopper_email":"[email protected]",18 "received_amount": "200",19 "received_currency": "SGD",20 "received_at": "2025-03-02T16:51:57+0000"21 },22 "type":"bank_transfer"23 }24 }25 },26 "created_at":"2025-02-27T09:52:07+0000",27 "version":"2024-02-22"28}
Since bank transfers depend on when and how much the customer sends, the actual received_amount may differ from the expected amount. Webhook status updates are also determined based on your reconciliation preferences.
| Description | Event |
|---|---|
| Customer transferred the exact amount. | payment_intent.succeededpayment_attempt.capture_requestedpayment_attempt.capture_amount = payment_intent.amount |
Customer transferred more than the expected amount. |
|
Customer transferred less than the expected amount. | payment_intent.succeededpayment_attempt.capture_requestedpayment_attempt.capture_amount = payment_attempt.received_amount |
Customer transferred less than the expected amount. |
|
No transfer or transfer received after expiry time. |
|
Test bank transfer in demo environment
You can simulate different payment and refund scenarios in the demo environment by creating different payment intent requests and then simulating your customer's action of sending a transfer. No actual bank transfers are required for this test.
POST /api/v1/simulation/pa/shopper_actions/transfer
JSON1{2 "account_number": "E1D5C0E9CB", \\REQUIRED3 "amount": "520", \\REQUIRED4 "reference": "D5NCUC" \\OPTIONAL5}
See example of how different models reconcile payments with incoming funds: