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
| Event | Trigger |
|---|---|
| invoice.completed | Payment fully confirmed; merchant balance credited |
| invoice.underpaid | Customer paid less than required (outside tolerance) |
| invoice.overpaid | Customer paid more than required; invoice still fulfilled |
| invoice.created | Invoice created via API (subscribe to start monitoring) |
| invoice.paid | Initial on-chain payment detected (pre-confirmation) |
| invoice.expired | Invoice TTL elapsed without sufficient payment |
| invoice.failed | Payment failed for any other reason |
| withdrawal.approved | Admin approved a merchant withdrawal request |
| withdrawal.completed | Withdrawal 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:
| Attempt | Delay |
|---|---|
| #1 | Immediate |
| #2 | 5 minutes |
| #3 | 30 minutes |
| #4 | 2 hours |
| #5 | 12 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"]
}