Vietnam Payment API Tutorial: Integrate Bank Transfers in Your App

Integrating Vietnamese bank transfer payments into your application is simpler than most developers expect. Vietnam's payment ecosystem runs on NAPAS โ€” a standardized interbank network โ€” which means a single API integration can cover 57 banks simultaneously. This tutorial walks through the complete integration process using RMPay's payment API.

Prerequisites

  • RMPay merchant account (contact @rmpay1 to get started)
  • Your merchant_id and api_key
  • A publicly accessible webhook URL on your server
  • Basic knowledge of HTTP requests and JSON

Code Examples

This tutorial uses Python and cURL examples. Full Python and PHP SDKs are available at RMPay developer SDK on GitHub.

Step 1: Create a Payment Order

When a customer reaches your checkout and selects bank transfer payment, your server creates a payment order via the RMPay API.

API Endpoint

POST https://api.rmpay.co/v1/order/create Headers: Content-Type: application/json X-API-Key: your_api_key

Request Body

{ "merchant_id": "your_merchant_id", "order_id": "ORD_20260714_001", // Your unique order ID "amount": 500000, // Amount in VND (min 10,000) "currency": "VND", "callback_url": "https://yoursite.com/webhook/rmpay", "description": "Order #ORD_20260714_001" // Optional }

Python Example

import requests def create_payment_order(order_id: str, amount: int) -> dict: response = requests.post( "https://api.rmpay.co/v1/order/create", headers={ "Content-Type": "application/json", "X-API-Key": "your_api_key" }, json={ "merchant_id": "your_merchant_id", "order_id": order_id, "amount": amount, "currency": "VND", "callback_url": "https://yoursite.com/webhook/rmpay" }, timeout=10 ) response.raise_for_status() return response.json()

Successful Response

{ "order_id": "ORD_20260714_001", "cashier_url": "https://pay.rmpay.io/pay?oid=abc123", "qr_code": "https://pay.rmpay.io/qr/abc123.png", "reference": "RMPAY001", "amount": 500000, "expires_at": "2026-07-14T10:30:00+07:00", "status": "pending" }

Step 2: Redirect Customer to Cashier

Redirect your customer to the cashier_url returned in the response. The RMPay cashier page displays:

  • A VietQR code compatible with all 57 Vietnamese banking apps
  • Bank transfer details (bank name, account number, reference code)
  • A countdown timer showing order expiry
  • Instructions in Vietnamese, English, or Chinese
# Python/Flask example from flask import redirect @app.route('/checkout/pay', methods=['POST']) def initiate_payment(): order = create_payment_order( order_id=generate_unique_order_id(), amount=int(request.form['amount']) ) # Store order in your database as 'pending' db.orders.update(order['order_id'], status='pending') # Redirect customer to payment page return redirect(order['cashier_url'])

Step 3: Handle Webhook Callback

This is the most critical part of the integration. When the customer completes payment, RMPay sends a POST request to your callback_url within seconds.

Webhook Payload

{ "event": "payment.success", "order_id": "ORD_20260714_001", "merchant_id": "your_merchant_id", "amount": 500000, "currency": "VND", "bank": "VCB", "reference": "RMPAY001", "status": "paid", "paid_at": "2026-07-14T09:53:02+07:00" }

Webhook Handler (Python/Flask)

import hmac, hashlib, json from flask import Flask, request, jsonify WEBHOOK_SECRET = "your_webhook_secret" @app.route('/webhook/rmpay', methods=['POST']) def rmpay_webhook(): # 1. Verify signature sig = request.headers.get('X-RMPay-Signature', '') expected = hmac.new( WEBHOOK_SECRET.encode(), request.data, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected, sig): return jsonify({'error': 'Unauthorized'}), 401 # 2. Parse event event = request.json if event['event'] == 'payment.success': order_id = event['order_id'] amount = event['amount'] # 3. Verify amount matches your order order = db.orders.get(order_id) if order['amount'] != amount: return jsonify({'error': 'Amount mismatch'}), 400 # 4. Mark as paid and fulfill db.orders.update(order_id, status='paid') fulfill_order(order_id) return jsonify({'status': 'ok'}), 200

Step 4: Query Order Status (Fallback)

Always implement a status polling fallback in case your webhook endpoint is temporarily down or a webhook is missed.

# Poll order status def check_order_status(order_id: str) -> str: response = requests.get( f"https://api.rmpay.co/v1/order/{order_id}", headers={"X-API-Key": "your_api_key"}, timeout=10 ) return response.json()['status'] # 'pending', 'paid', 'expired'

Security Best Practices

Always Verify Webhook Signatures

Never process a webhook without verifying the HMAC signature. Anyone can POST to your webhook endpoint โ€” only RMPay's signed requests should trigger order fulfillment.

Verify Amount in Webhook

Always cross-check the amount in the webhook against your stored order amount. Never fulfill an order if the amounts don't match exactly.

Idempotent Webhook Handling

Webhooks may occasionally be delivered more than once. Make your handler idempotent โ€” check if the order is already marked as paid before processing again.

# Idempotent handler example if event['event'] == 'payment.success': order = db.orders.get(event['order_id']) if order['status'] == 'paid': return jsonify({'status': 'already_processed'}), 200 # Process payment...

Use HTTPS for Webhook URL

Your webhook endpoint must use HTTPS. RMPay will not deliver webhooks to HTTP endpoints.

Testing Your Integration

Before going live, test thoroughly in the sandbox environment:

  1. Use sandbox API credentials (provided on merchant onboarding)
  2. Create test orders with small amounts
  3. Simulate payment confirmation via the sandbox dashboard
  4. Verify your webhook handler processes events correctly
  5. Test error cases: expired orders, amount mismatches, invalid signatures

Going Live Checklist

  • โœ… Webhook endpoint on HTTPS and publicly accessible
  • โœ… Signature verification implemented and tested
  • โœ… Amount verification in webhook handler
  • โœ… Idempotent webhook handling
  • โœ… Order status polling as fallback
  • โœ… Error logging for failed webhooks
  • โœ… Switch API credentials from sandbox to production

Ready to Integrate Vietnam Payments?

Contact RMPay to receive your API credentials and sandbox access. Most integrations complete in 2โ€“4 hours. 57 Vietnamese banks, real-time settlement.

Get API Access โ†’

Related resources:
โ†’ Python SDK on GitHub
โ†’ PHP SDK on GitHub
โ†’ RMPay How It Works
โ†’ Vietnam Bank Transfer Guide