NexusPay· Integration Guide

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.

You'll need: your 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.

Verify your key works
curl https://nexuspayph.com/v1/ping

No auth needed for ping — it just confirms the API is reachable:

Response (200)
{
  "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.

List methods
curl https://nexuspayph.com/v1/methods   -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
Response (200)
{
  "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.

Create a ₱500 GCash payment
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:

Response (201 Created)
{
  "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_amount fills 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 inOutcome
.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:

Simulate a successful payment
curl -X POST https://nexuspayph.com/wapi/sandbox/transition   -H "Content-Type: application/json"   -d '{
    "paymentId": "pay_8e64e0c3973e232206249eec",
    "outcome": "succeeded"
  }'
Response (200)
{
  "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.

Retrieve the payment
curl https://nexuspayph.com/v1/payments/pay_8e64e0c3973e232206249eec   -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
Response (200 — succeeded)
{
  "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_amount487.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).

Register a webhook endpoint
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"
  }'
Response (201)
{
  "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
}
Save the 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.

Node.js verification
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:

Webhook payload (payment.succeeded)
{
  "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.

List recent payments
curl "https://nexuspayph.com/v1/payments?limit=5"   -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
Response (200)
{
  "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
}
List events (for reconciliation)
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.

Get balance
curl https://nexuspayph.com/v1/balance   -H "Authorization: Bearer sk_test_VfXgvRWebiAfeu-9xnHxJ3_fHayoS-vU"
Response (200)
{
  "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:

  1. Swap your API key: replace sk_test_… with yoursk_live_… key (from the dashboard Developer tab).
  2. Remove sandbox calls: the /wapi/sandbox/transition endpoint from Step 4 is test-only — in live mode, real customers pay via GCash and the payment settles automatically. Just poll GET /v1/payments/{id} or wait for the webhook.
  3. Register a live webhook: repeat Step 6 with your sk_live_ key and your production webhook URL. Save the new whsec_ — test and live webhook secrets are separate.
That's it. No code changes beyond the key swap — the API surface is identical between test and live. Your sandbox-passing integration works in production.

Quick reference

WhatEndpointAuth
Health checkGET /v1/pingnone
List methodsGET /v1/methodsBearer sk_
Create paymentPOST /v1/paymentsBearer sk_
Retrieve paymentGET /v1/payments/{id}Bearer sk_
List paymentsGET /v1/paymentsBearer sk_
Register webhookPOST /v1/webhooks/endpointsBearer sk_
List eventsGET /v1/eventsBearer sk_
Get balanceGET /v1/balanceBearer sk_
Full API reference/docs/api