Webhooks

Receive real-time notifications when invoice status changes. All webhooks are signed with HMAC-SHA256.

Signature verification

Every webhook POST includes an X-Oxinpay-Signature header. Compute the expected signature and compare to reject forged requests.

Node.jsPHPPython
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express example
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-oxinpay-signature'];
  if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  const event = JSON.parse(req.body);
  // handle event...
  res.json({ received: true });
});

Event types

EventTrigger
invoice.completedPayment fully confirmed; merchant balance credited
invoice.underpaidCustomer paid less than required (outside tolerance)
invoice.overpaidCustomer paid more than required; invoice still fulfilled
invoice.createdInvoice created via API (subscribe to start monitoring)
invoice.paidInitial on-chain payment detected (pre-confirmation)
invoice.expiredInvoice TTL elapsed without sufficient payment
invoice.failedPayment failed for any other reason
withdrawal.approvedAdmin approved a merchant withdrawal request
withdrawal.completedWithdrawal transaction confirmed on-chain

Payload schema

{
  "event": "invoice.completed",
  "timestamp": "2026-06-21T12:00:00Z",
  "data": {
    "invoiceId": "clxyz123...",
    "publicId": "pub_abc123",
    "status": "completed",
    "amountFiat": "50.00",
    "fiatCurrency": "USD",
    "amountCrypto": "47.321000",
    "tokenSymbol": "USDT",
    "chainCode": "BSC",
    "txHash": "0xabc...",
    "confirmations": 15,
    "depositAddress": "0xdef...",
    "merchantReference": "order_1234",
    "metadata": {},
    "paidAt": "2026-06-21T12:01:30Z"
  }
}

Retry policy

If your endpoint returns a non-2xx response or times out (30s), Oxin Pay retries with exponential backoff:

AttemptDelay
#1Immediate
#25 minutes
#330 minutes
#42 hours
#512 hours

After 5 failed attempts, the delivery is marked as failed. You can view all delivery attempts in the dashboard.

Registering endpoints

POST /web/v1/webhook-endpoints
{
  "url": "https://yoursite.com/oxinpay-webhook",
  "events": ["invoice.completed", "invoice.expired"]
}