Integration Guide
Everything you need to go from zero to your first successful payment — step by step, with real curl commands and the actual responses you'll see. Copy each command, run it in your terminal, and follow along.
sk_test_ secret key from the NexusPay dashboard (Developer → API Keys). This guide uses real test credentials — no real money moves in test mode.Step 1 — Authenticate
Every API call carries your secret key as a Bearer token. Test keys (sk_test_) only affect sandbox payments; live keys (sk_live_) only affect real money. Never expose your secret key in client-side code — use it only on your server.
Your publishable key (pk_test_) is for embed widgets and client-side identification — it cannot create payments or access your balance.
curl https://nexuspayph.com/v1/ping
No auth needed for ping — it just confirms the API is reachable:
{
"ok": true,
"time": 1786859384
}Step 2 — Check available payment methods
Before creating a payment, check which methods are enabled for your key's environment. In test mode, every method routes through the NexusPay Sandbox (mock) — no real GCash or bank is contacted.
curl https://nexuspayph.com/v1/methods -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
{
"data": [
{
"method": "gcash_qr",
"currencies": ["PHP"],
"providers": [
{ "code": "mock", "display_name": "NexusPay Sandbox" }
]
},
{
"method": "card",
"currencies": ["PHP"],
"providers": [
{ "code": "mock", "display_name": "NexusPay Sandbox" }
]
},
{
"method": "maya",
"currencies": ["PHP"],
"providers": [
{ "code": "mock", "display_name": "NexusPay Sandbox" }
]
}
]
}The providers list shows who carries each method. In test mode it's always the sandbox. In live mode you'll see the real upstream providers (NetCorePay, DirectPay).
Step 3 — Create a payment
This is the core call. You tell NexusPay the amount, method, and where to send the customer after they pay. NexusPay returns a checkout_url — redirect your customer there to complete the payment.
Always send an Idempotency-Key header. If your network retries (timeout, 5xx), NexusPay returns the original response instead of creating a duplicate charge.
curl -X POST https://nexuspayph.com/v1/payments -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU" -H "Content-Type: application/json" -H "Idempotency-Key: my-order-1042" -d '{
"amount": 500.00,
"currency": "PHP",
"method": "gcash_qr",
"description": "Order #1042",
"return_url": "https://magia.ph/payment/success",
"metadata": {
"order_id": "MAGIA-1042",
"player": "test_player_01"
}
}'The response includes everything you need to redirect the customer and track the payment:
{
"id": "pay_8e64e0c3973e232206249eec",
"object": "payment",
"environment": "test",
"status": "pending",
"amount": 500,
"currency": "PHP",
"method": "gcash_qr",
"provider": "mock",
"provider_ref": "mck_40c56d3fcea6a47fd5b1",
"checkout_url": "https://nexuspayph.com/sandbox/checkout/pay_8e64e0c3973e232206249eec",
"return_url": "https://magia.ph/payment/success",
"description": "Order #1042",
"customer": null,
"metadata": {
"order_id": "MAGIA-1042",
"player": "test_player_01"
},
"fee_amount": 12.5,
"net_amount": null,
"idempotency_key": "my-order-1042",
"failure": null,
"qr_code": null,
"qr_code_url": null,
"app_link": null,
"expires_at": 1786860750,
"succeeded_at": null,
"failed_at": null,
"created": 1786859850,
"receipt_url": null
}Key fields:
id— the payment ID. Save it; you'll use it to check status and receive webhooks.checkout_url— redirect the customer here. In test mode this opens the sandbox page.status: "pending"— the customer hasn't paid yet.fee_amount: 12.5— the platform fee (2.5% of ₱500).net_amountfills in on success.expires_at— unix timestamp; the checkout expires after 15 minutes if unpaid.
Step 4 — The customer pays (sandbox)
In production, the customer scans the GCash QR or enters their details on the checkout page and the payment settles automatically. In test mode, you simulate this.
There are two ways to drive sandbox outcomes:
Option A — Deterministic amounts: the cents portion of the amount controls the outcome automatically:
| Amount ends in | Outcome |
|---|---|
.00 (e.g. 500.00) | Automatic success |
.01 (e.g. 500.01) | Automatic failure |
.99 (e.g. 500.99) | Checkout expires (unpaid) |
Option B — Sandbox transition API: for amounts that don't end in a special value, explicitly trigger the outcome:
curl -X POST https://nexuspayph.com/wapi/sandbox/transition -H "Content-Type: application/json" -d '{
"paymentId": "pay_8e64e0c3973e232206249eec",
"outcome": "succeeded"
}'{
"ok": true,
"changed": true
}Step 5 — Retrieve the payment (poll for status)
After the customer pays, retrieve the payment to confirm it succeeded. Thenet_amount is now populated (amount − fee), and a receipt_urlis available to share with the customer.
curl https://nexuspayph.com/v1/payments/pay_8e64e0c3973e232206249eec -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
{
"id": "pay_8e64e0c3973e232206249eec",
"object": "payment",
"environment": "test",
"status": "succeeded",
"amount": 500,
"currency": "PHP",
"method": "gcash_qr",
"provider": "mock",
"checkout_url": "https://nexuspayph.com/sandbox/checkout/pay_8e64e0c3973e232206249eec",
"description": "Order #1042",
"metadata": {
"order_id": "MAGIA-1042",
"player": "test_player_01",
"or_number": "OR-2026-001009"
},
"fee_amount": 12.5,
"net_amount": 487.5,
"idempotency_key": "my-order-1042",
"failure": null,
"expires_at": 1786860750,
"succeeded_at": 1786859872,
"failed_at": null,
"created": 1786859850,
"receipt_url": "https://nexuspayph.com/receipt/pay_8e64e0c3973e232206249eec?token=..."
}What changed:
status→"succeeded"net_amount→487.5(₱500 − ₱12.50 fee)succeeded_at→ populated (unix timestamp)receipt_url→ shareable receipt link (only on terminal states)
Step 6 — Set up webhooks (get notified)
Polling works, but webhooks are the recommended way to know when a payment settles — NexusPay POSTs to your server the moment the status changes. Register a webhook endpoint and save the secret it returns (shown only once).
curl -X POST https://nexuspayph.com/v1/webhooks/endpoints -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU" -H "Content-Type: application/json" -d '{
"url": "https://magia.ph/api/webhooks/nexuspay",
"events": ["payment.succeeded", "payment.failed", "payment.expired"],
"description": "Magia webhook"
}'{
"id": "whe_24cd5198ad0079c232d6",
"object": "webhook_endpoint",
"url": "https://magia.ph/api/webhooks/nexuspay",
"events": ["payment.succeeded", "payment.failed", "payment.expired"],
"secret": "whsec_N-RmA22AT6jxnSD3fpl4NweuUjrDLHzeiMb7lQ",
"enabled": true,
"created": 1786859893
}secret immediately. It is shown only once — you'll need it to verify every incoming webhook. If you lose it, register a new endpoint.Step 7 — Verify webhook signatures
Every webhook NexusPay sends includes an X-NexusPay-Signature header. Always verify it — never trust an unsigned request. The format ist=<timestamp>,v1=<hex> — an HMAC-SHA256 of"timestamp.rawBody" using your whsec_ secret.
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(kv => kv.split('='))
);
// Reject stale timestamps (replay protection)
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(parts.t + '.' + rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(parts.v1, 'hex')
);
}
// Express example:
// app.post('/api/webhooks/nexuspay', (req, res) => {
// const rawBody = req.rawBody; // use raw body, not JSON.parse'd
// const sig = req.headers['x-nexuspay-signature'];
// if (!verifyWebhook(rawBody, sig, WHSEC)) return res.status(403).send('forbidden');
// const event = JSON.parse(rawBody);
// if (event.type === 'payment.succeeded') {
// fulfillOrder(event.data.object.id);
// }
// res.status(200).send('ok');
// });The webhook body (after verification) looks like this:
{
"id": "evt_5bc89b72a2f6976e117e21f6",
"object": "event",
"type": "payment.succeeded",
"environment": "test",
"created": 1786859872,
"data": {
"object": {
"id": "pay_8e64e0c3973e232206249eec",
"object": "payment",
"status": "succeeded",
"amount": 500,
"currency": "PHP",
"method": "gcash_qr",
"metadata": {
"order_id": "MAGIA-1042"
},
"fee_amount": 12.5,
"net_amount": 487.5
}
}
}The data.object.id matches the payment id from Step 3. Use it to look up the full payment record in your database. Non-2xx responses trigger retries at 1m, 5m, 30m, 2h, 6h, 24h (6 attempts).
Step 8 — List payments and events
Use list endpoints to reconcile or build admin dashboards. Both support cursor-based pagination with limit (max 100) and starting_after.
curl "https://nexuspayph.com/v1/payments?limit=5" -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
{
"object": "list",
"data": [
{
"id": "pay_8e64e0c3973e232206249eec",
"object": "payment",
"status": "succeeded",
"amount": 500,
"currency": "PHP",
"method": "gcash_qr",
"fee_amount": 12.5,
"net_amount": 487.5,
"created": 1786859850
}
],
"has_more": false
}curl "https://nexuspayph.com/v1/events?limit=5&type=payment.succeeded" -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
Step 9 — Check your balance
Your available balance reflects successful payments minus fees and payouts. Query it any time — it's keyed by currency.
curl https://nexuspayph.com/v1/balance -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
{
"object": "balance_list",
"environment": "test",
"available": {
"PHP": 100
}
}The available object is currency-keyed: { "PHP": 487.5 }. For a single currency, pass ?currency=PHP to get a flat number.
Step 10 — Go live
When your sandbox integration is working end-to-end, switching to live is three changes:
- Swap your API key: replace
sk_test_…with yoursk_live_…key (from the dashboard Developer tab). - Remove sandbox calls: the
/wapi/sandbox/transitionendpoint from Step 4 is test-only — in live mode, real customers pay via GCash and the payment settles automatically. Just pollGET /v1/payments/{id}or wait for the webhook. - Register a live webhook: repeat Step 6 with your
sk_live_key and your production webhook URL. Save the newwhsec_— test and live webhook secrets are separate.
Quick reference
| What | Endpoint | Auth |
|---|---|---|
| Health check | GET /v1/ping | none |
| List methods | GET /v1/methods | Bearer sk_ |
| Create payment | POST /v1/payments | Bearer sk_ |
| Retrieve payment | GET /v1/payments/{id} | Bearer sk_ |
| List payments | GET /v1/payments | Bearer sk_ |
| Register webhook | POST /v1/webhooks/endpoints | Bearer sk_ |
| List events | GET /v1/events | Bearer sk_ |
| Get balance | GET /v1/balance | Bearer sk_ |
| Full API reference | /docs/api | — |