Accept a QRPH Payment
This guide shows you how to generate a one-time QRPH code for a specific amount and handle the payment confirmation webhook.
Use this pattern for checkout flows where the customer owes a known amount — invoices, order payments, subscription renewals.
Prerequisites
- A merchant and account already created (Wallet Structure)
- A webhook endpoint set up and registered (Setup Webhooks)
- Sandbox credentials from /sandbox
Step 1: Create a Payment Intent
A Payment Intent generates a single-use QRPH code for a specific amount. The code expires once paid or when you cancel it.
- cURL
- Node.js
- Python
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-X POST "https://api.partners.nextpay.world/v2/payment-intents" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: order-98765-pi-v1" \
-d '{
"account_id": "YOUR_ACCOUNT_UUID",
"amount": 150000,
"currency": "PHP/2",
"payment_instrument_options": {
"method_type": "qrph_p2m_reference",
"method_provider": "automatic"
},
"external_id": "order-98765"
}'
const response = await fetch('https://api.partners.nextpay.world/v2/payment-intents', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + Buffer.from('YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').toString('base64'),
'Content-Type': 'application/json',
'X-Idempotency-Key': 'order-98765-pi-v1',
},
body: JSON.stringify({
account_id: 'YOUR_ACCOUNT_UUID',
amount: 150000, // PHP 1,500.00 in centavos
currency: 'PHP/2',
payment_instrument_options: {
method_type: 'qrph_p2m_reference',
method_provider: 'automatic',
},
external_id: 'order-98765',
}),
});
const paymentIntent = await response.json();
import requests
import base64
credentials = base64.b64encode(b'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').decode()
response = requests.post(
'https://api.partners.nextpay.world/v2/payment-intents',
headers={
'Authorization': f'Basic {credentials}',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'order-98765-pi-v1',
},
json={
'account_id': 'YOUR_ACCOUNT_UUID',
'amount': 150000, # PHP 1,500.00 in centavos
'currency': 'PHP/2',
'payment_instrument_options': {
'method_type': 'qrph_p2m_reference',
'method_provider': 'automatic',
},
'external_id': 'order-98765',
}
)
payment_intent = response.json()
Response
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"account_id": "123e4567-e89b-12d3-a456-426614174000",
"external_id": "order-98765",
"amount": 150000,
"currency": "PHP/2",
"status": "pending",
"expires_at": "2025-11-15T10:35:00Z",
"created_at": "2025-11-15T10:30:00Z",
"payment_instrument": {
"id": "123e4567-e89b-12d3-a456-426614174001",
"method_type": "qrph_p2m_reference",
"method_provider": "ph_netbank",
"method_details": {
"merchant_name": "Your Merchant Name",
"reference_label": "...",
"routing_account": "...",
"amount": { "value": 150000, "currency": "PHP/2" },
"resolution": 480
},
"status": "active",
"persistence_mode": "temporary",
"usage_mode": "single"
}
}
Key fields:
payment_instrument.method_details— contains the QRPH data for rendering a QR code client-sideexternal_id— your own reference, returned in the webhook so you can match the payment to your order
Step 2: Display the QR code
The raw QRPH data is in payment_intent.payment_instrument.method_details. Use a QR code library (e.g., qrcode) to render it client-side.
<!-- Render the QR code from method_details using a QR library -->
<canvas id="qr-canvas"></canvas>
<script>
// Use payment_intent.payment_instrument.method_details with a QRPH-compatible QR library
// Example: QRCode.toCanvas(document.getElementById('qr-canvas'), methodDetailsData)
</script>
The QR code is compatible with all Philippine bank apps and e-wallets (GCash, Maya, BPI, BDO, UnionBank, and more).
Step 3: Handle the webhook
When the customer pays, NextAPI sends a v2.payment_intent.succeeded event to your registered webhook endpoint.
app.post('/webhooks/nextapi', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-nextpay-signature'];
// Always verify the signature first
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body);
if (event.event === 'v2.payment_intent.succeeded') {
const { id, external_id, amount, status } = event.payload;
// external_id is the value you set when creating the payment intent
// Use it to match this payment to your order
await markOrderPaid(external_id, { paymentIntentId: id, amount });
}
res.status(200).send('OK');
});
→ See Setup Webhooks for full signature verification code.
Step 4: (Optional) Poll for status
If you need to check payment status without relying on webhooks — for example, after a network interruption — poll the Payment Intent directly:
- cURL
- Node.js
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
"https://api.partners.nextpay.world/v2/payment-intents/YOUR_PAYMENT_INTENT_ID"
const response = await fetch('https://api.partners.nextpay.world/v2/payment-intents/YOUR_PAYMENT_INTENT_ID', {
headers: {
'Authorization': 'Basic ' + Buffer.from('YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').toString('base64'),
},
});
const intent = await response.json();
// intent.status: "pending" | "succeeded" | "canceled" | "expired"
You can also look up by your own reference with GET /v2/payment-intents/external/{external_id}.
Payment Intent states
| Status | Meaning |
|---|---|
pending | Created, waiting for payment |
succeeded | Customer paid — funds in the account |
canceled | Canceled via PATCH /v2/payment-intents/{id}/cancel |
expired | Not paid within the expiry window (default 5 minutes / 300s, max 30 minutes / 1800s) |
Key points
- Amounts are in centavos. PHP 1,500.00 =
150000. Never use decimal amounts. - Always set
external_idto your internal order/invoice reference — it comes back in the webhook. - One Payment Intent = one payment. Don't reuse a payment intent for a different transaction.
- Idempotency key prevents duplicate payment intents if your request fails and you retry.
Related
- Setup Virtual Collection (static QR) — persistent QR for recurring collection
- Collections Lifecycle — full state machine
- Verify Webhook Signatures — signature verification in depth