Skip to main content

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

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 -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": "acct_01HXYZ",
"amount": 150000,
"description": "Order #98765",
"external_id": "order-98765"
}'

Response

{
"id": "pi_01HXYZ",
"account_id": "acct_01HXYZ",
"amount": 150000,
"status": "pending",
"description": "Order #98765",
"external_id": "order-98765",
"qr_code": "00020101021226...",
"qr_image_url": "https://api.partners.nextpay.world/v2/payment-intents/pi_01HXYZ/qr",
"created_at": "2025-11-15T10:30:00Z",
"expires_at": "2025-11-15T11:30:00Z"
}

Key fields:

  • qr_code — the raw QRPH payload string (for embedding in your own QR renderer)
  • qr_image_url — a pre-rendered QR image you can display directly
  • external_id — your own reference, returned in the webhook so you can match the payment to your order

Step 2: Display the QR code

Show qr_image_url to the customer in your checkout UI, or render qr_code with a QR library if you need custom styling.

<!-- Direct image display -->
<img src="https://api.partners.nextpay.world/v2/payment-intents/pi_01HXYZ/qr"
alt="Scan to pay PHP 1,500.00"
width="256" height="256" />

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 payment_intent.paid 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.type === 'payment_intent.paid') {
const { id, external_id, amount, status } = event.data;

// 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 -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
"https://api.partners.nextpay.world/v2/payment-intents/pi_01HXYZ"

You can also look up by your own reference with GET /v2/payment-intents/external/{external_id}.

Payment Intent states

StatusMeaning
pendingCreated, waiting for payment
paidCustomer paid — funds in the account
cancelledCancelled via POST /v2/payment-intents/{id}/cancel
expiredNot paid within the expiry window (default: 1 hour)

Key points

  • Amounts are in centavos. PHP 1,500.00 = 150000. Never use decimal amounts.
  • Always set external_id to 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.