Documentation Index
Fetch the complete documentation index at: https://neilyan.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
XPayLabs sends HTTP POST callbacks to your configured endpoint URL whenever an order or collection changes state. Instead of polling the API for status changes, register your callback URL and receive signed notifications in real-time.
Configuration
Set your webhook endpoint URL and shared secret in the gateway configuration:
xpay:
merchant:
callback-url: "https://yourapp.com/webhooks/xpaylabs"
webhook-secret: "your-webhook-secret-here"
The webhook-secret is used to compute the sign field in each webhook payload, allowing you to verify the notification originated from your gateway.
Every webhook callback is a POST request with a JSON body following the NotifyPayload structure:
| Field | Type | Description |
|---|
sign | string | HMAC-SHA256 signature for payload verification |
timestamp | integer | Unix timestamp of when the notification was generated |
nonce | string | Unique event identifier |
notifyType | string | The event type (see Event Types) |
data | object | The event payload (NotifyOrder object) |
Example Payload
{
"sign": "a1b2c3d4e5f6...",
"timestamp": 1717000123,
"nonce": "550e8400-e29b-41d4-a716-446655440000",
"notifyType": "ORDER_SUCCESS",
"data": {
"orderId": "order_1042",
"uid": "user_42",
"orderType": "COLLECTION",
"status": "SUCCESS",
"reason": null,
"amount": "250.00",
"actualAmount": "249.50",
"fee": "0.00",
"transaction": {
"chain": "TRON",
"symbol": "USDT",
"txid": "a1b2c3d4e5f6...",
"from": "TXyz...",
"to": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR",
"amount": "250.00",
"blockNum": 12345678,
"confirmedNum": 3,
"status": "SUCCESS",
"timestamp": 1717000123
}
}
}
Signature Verification
Verify the webhook signature using your webhook-secret:
Algorithm
- Serialize the
data object (the data field from the payload) as compact JSON.
- Compute
HMAC-SHA256(data_json, webhook_secret).
- Convert to lowercase hex and compare with the
sign field.
Node.js
import crypto from "crypto";
export function verifyWebhook(payload, secret) {
const dataJson = JSON.stringify(payload.data); // compact JSON
const expectedSign = crypto
.createHmac("sha256", secret)
.update(dataJson, "utf8")
.digest("hex");
if (!crypto.timingSafeEqual(
Buffer.from(expectedSign, "hex"),
Buffer.from(payload.sign, "hex")
)) {
throw new Error("Invalid webhook signature");
}
return true;
}
// Express.js example
app.post("/webhooks/xpaylabs", express.json(), (req, res) => {
try {
verifyWebhook(req.body, process.env.XPAYLABS_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send("Verification failed");
}
const event = req.body;
// Process event...
res.status(200).send("ok");
});
Python
import hmac, hashlib, json
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"
@app.route("/webhooks/xpaylabs", methods=["POST"])
def handle_webhook():
payload = request.get_json()
data_json = json.dumps(payload["data"], separators=(",", ":"))
expected_sign = hmac.new(
WEBHOOK_SECRET.encode(),
data_json.encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected_sign, payload["sign"]):
return "Invalid signature", 400
notify_type = payload["notifyType"]
# Process event...
return "", 200
Retry Policy
If your endpoint does not return a 2xx status code within 10 seconds, XPayLabs retries the webhook with exponential backoff:
| Attempt | Delay |
|---|
| 1st retry | ~1 second |
| 2nd retry | ~5 seconds |
| 3rd retry | ~30 seconds |
| 4th retry | ~5 minutes |
After 4 failed attempts, the notification is marked as failed. Failed webhooks are logged and can be replayed from the gateway dashboard.
Best Practices
- Respond immediately. Return
200 as fast as possible, then process the event asynchronously.
- Verify every payload. Always check the HMAC signature before acting on a webhook.
- Use
nonce for deduplication. Store processed nonces to handle at-least-once delivery.
- Check
notifyType. Route events based on the notification type to handle each event correctly.