Sample Integration

A minimal Node.js + Express merchant that creates invoices, redirects to hosted checkout, and handles IPN webhooks — everything you need to integrate Oxin Pay in under 100 lines.

Payment flow

Customer clicks PayPOST /pay (your server)POST /v1/invoices (Oxin Pay)Redirect to checkoutUrlCustomer paysWebhook → POST /webhookVerify HMAC → fulfil order
1

Install and configure

Clone the sample from examples/sample-merchant/, install deps, and set your credentials.

cd examples/sample-merchant
npm install
cp .env.example .env
# Edit .env with your API key and webhook secret
OXIN_PAY_API_KEY=oxp_live_YOUR_KEY_HERE
OXIN_PAY_BASE_URL=https://pay.oxinchain.io
OXIN_PAY_WEBHOOK_SECRET=your_webhook_secret
2

Create an invoice

Call POST /v1/invoices with your Bearer key. The response contains a checkoutUrl — redirect the customer there.

POST /v1/invoices
Authorization: Bearer oxp_live_...

{
  "tokenId":           2,
  "amountFiat":        5.00,
  "fiatCurrency":      "USD",
  "description":       "Digital Access Pass",
  "merchantReference": "order-1234",
  "redirectUrl":       "https://yourstore.com/thank-you",
  "cancelUrl":         "https://yourstore.com/cart"
}
3

Verify webhook signatures

Every IPN POST carries an X-Oxinpay-Signature: sha256=<hex> header. Compute HMAC-SHA256(endpoint_secret, raw_body) and compare with constant-time equality.

Headers

X-Oxinpay-Event      invoice.paid
X-Oxinpay-Signature  sha256=<hmac-sha256-hex>
X-Oxinpay-Timestamp  1718000000
X-Oxinpay-Delivery   01J123456789ABCDEFGHJKMNPQ

Body (invoice.paid)

{
  "publicId":         "01J123456789ABCDEFGHJKMNPQ",
  "merchantReference":"order-1718000000",
  "status":           "paid",
  "amountFiat":       "5.00",
  "fiatCurrency":     "USD",
  "tokenId":          2,
  "symbol":           "USDT",
  "chainCode":        "BSC",
  "amountCrypto":     "5.012345678901234567",
  "amountReceived":   "5.012345678901234567",
  "depositAddress":   "0xabc...",
  "paidAt":           "2024-06-10T12:00:00.000Z"
}
4

Full server code

The complete sample server — invoice creation, status polling, and webhook verification.

import 'dotenv/config';
import express from 'express';
import crypto from 'node:crypto';

const { OXIN_PAY_API_KEY, OXIN_PAY_BASE_URL, OXIN_PAY_WEBHOOK_SECRET } = process.env;

const app = express();
app.use('/webhook', express.raw({ type: 'application/json' }));
app.use(express.json());

// Helper: call Oxin Pay API
async function oxinPayRequest(method, path, body) {
  const res = await fetch(`${OXIN_PAY_BASE_URL}/v1${path}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${OXIN_PAY_API_KEY}`,
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const json = await res.json();
  if (!json.ok) throw new Error(json.error?.message ?? 'API error');
  return json.data;
}

// POST /pay — create invoice and redirect to hosted checkout
app.post('/pay', async (req, res) => {
  const invoice = await oxinPayRequest('POST', '/invoices', {
    tokenId:      2,          // USDT-BSC; use /v1/invoices/currencies to list options
    amountFiat:   5.00,
    fiatCurrency: 'USD',
    merchantReference: `order-${Date.now()}`,
    redirectUrl:  'https://yourstore.com/thank-you',
    cancelUrl:    'https://yourstore.com/cart',
  });
  res.redirect(302, invoice.checkoutUrl);
});

// GET /status/:publicId — poll invoice status
app.get('/status/:publicId', async (req, res) => {
  const invoice = await oxinPayRequest('GET', `/invoices/${req.params.publicId}`);
  res.json({ status: invoice.status, amountReceived: invoice.amountReceived });
});

// POST /webhook — receive and verify IPN events
app.post('/webhook', (req, res) => {
  const rawBody   = req.body;                               // Buffer
  const sigHeader = req.headers['x-oxinpay-signature'];     // "sha256=<hex>"
  const event     = req.headers['x-oxinpay-event'];

  if (!verifySignature(rawBody, sigHeader)) {
    return res.status(400).json({ error: 'invalid_signature' });
  }

  const payload = JSON.parse(rawBody.toString());
  if (event === 'invoice.paid') {
    // Fulfil order for payload.publicId
  }
  res.status(200).json({ ok: true });
});

function verifySignature(rawBody, sigHeader) {
  const [algo, provided] = sigHeader?.split('=') ?? [];
  if (algo !== 'sha256' || !provided) return false;
  const expected = crypto
    .createHmac('sha256', OXIN_PAY_WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(provided, 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.listen(3000);
5

Run the integration test

Use the provided script to verify every API step against a live backend.

export OXIN_PAY_API_KEY="oxp_live_..."
export OXIN_PAY_BASE_URL="https://pay.oxinchain.io"
bash infra/scripts/test-live-integration.sh

Webhook events

EventWhenAction
invoice.createdInvoice created by merchantOptional: send receipt
invoice.paidFull amount received on-chainFulfil order immediately
invoice.completedFunds swept to merchant hot walletOptional: update accounting
invoice.expiredTTL elapsed with no paymentCancel or re-create order
invoice.underpaidPartial payment receivedContact customer or refund

Security checklist

  • Always verify the webhook HMAC before fulfilling an order
  • Use crypto.timingSafeEqual for signature comparison
  • Process webhooks idempotently — use X-Oxinpay-Delivery as a dedup key
  • Never trust invoice.paid if the amounts do not match your expected amount
  • Store your API key in an environment variable, never hard-coded
  • Set an IP whitelist in the dashboard to limit API key usage to your server IPs