Webhooks
Receive real-time notifications when payment events occur. All webhooks are signed with HMAC-SHA256 for security.
HMAC Signed
Verify authenticity
Auto Retry
5 attempts on failure
Real-time
Instant delivery
Setting Up Webhooks
- 1
Configure Webhook URL
Set your webhook URL in the merchant dashboard or include it in the payment request.
- 2
Store Your Secret
Keep your API secret secure - you'll use it to verify webhook signatures.
- 3
Implement Handler
Create an endpoint that verifies the signature and processes the event.
Verifying Signatures
All webhooks include a signature header. Always verify this signature before processing:
const crypto = require('crypto');
function verifyWebhook(req, webhookSecret) {
const signature = req.headers['x-pulse2pay-signature'];
const timestamp = req.headers['x-pulse2pay-timestamp'];
// Use raw body string for signature verification
const body = req.rawBody || JSON.stringify(req.body);
// Check timestamp is within 5 minutes
const now = Date.now();
if (Math.abs(now - parseInt(timestamp)) > 300000) {
throw new Error('Webhook timestamp too old');
}
// Verify signature (webhook uses: timestamp + "." + body)
// Note: This is different from API signature format!
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(timestamp + '.' + body);
const expectedSignature = hmac.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
throw new Error('Invalid webhook signature');
}
return true;
}Important: Webhook signatures use a different format than API signatures. Webhook: timestamp + "." + body. Always verify the signature before processing webhook events.
Webhook Events
payment.createdA new payment has been created
Example Payload
{
"id": "evt_a1b2c3d4_1705078200000",
"type": "payment.created",
"createdAt": "2025-01-12T15:00:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"tokenStandard": "TRC20",
"generatedAddress": "TXyz...abc123",
"generatedTokenAccount": null,
"addressMode": "PER_PAYMENT",
"merchantUserId": null,
"expectedAmount": "100.50",
"metadata": { "orderId": "1001" },
"createdAt": "2025-01-12T15:00:00.000Z",
"expiresAt": "2025-01-12T15:30:00.000Z"
}
}payment.pendingPayment is pending blockchain confirmations
Example Payload
{
"id": "evt_a1b2c3d4_1705078300000",
"type": "payment.pending",
"createdAt": "2025-01-12T15:01:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"tokenStandard": "TRC20",
"generatedAddress": "TXyz...abc123",
"generatedTokenAccount": null,
"txHash": "abc123def456...",
"confirmations": 5,
"addressMode": "PER_PAYMENT",
"merchantUserId": null,
"expectedAmount": "100.50",
"receivedAmount": "100.50",
"metadata": { "orderId": "1001" },
"createdAt": "2025-01-12T15:00:00.000Z",
"updatedAt": "2025-01-12T15:01:00.000Z",
"expiresAt": "2025-01-12T15:30:00.000Z"
}
}payment.confirmedPayment has been confirmed on the blockchain
Example Payload
{
"id": "evt_a1b2c3d4_1705078500000",
"type": "payment.confirmed",
"createdAt": "2025-01-12T15:05:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "confirmed",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"tokenStandard": "TRC20",
"txHash": "abc123def456...",
"externalId": "generated-id",
"generatedAddress": "TXyz...abc123",
"generatedTokenAccount": null,
"confirmations": 19,
"addressMode": "PER_PAYMENT",
"merchantUserId": null,
"expectedAmount": "100.50",
"receivedAmount": "100.50",
"differenceAmount": "0",
"overpaidAmount": "0",
"underpaidAmount": "0",
"alertType": "NONE",
"feeAmount": "1.005",
"netAmount": "99.495",
"metadata": { "orderId": "1001" },
"createdAt": "2025-01-12T15:00:00.000Z",
"updatedAt": "2025-01-12T15:05:00.000Z",
"expiresAt": "2025-01-12T15:30:00.000Z"
}
}payment.underpaidReceived amount was less than expected
Example Payload
{
"id": "evt_a1b2c3d4_1705078500000",
"type": "payment.underpaid",
"createdAt": "2025-01-12T15:05:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "underpaid",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"expectedAmount": "100.50",
"receivedAmount": "90.00",
"differenceAmount": "10.50",
"underpaidAmount": "10.50",
"alertType": "UNDERPAID",
"txHash": "abc123def456...",
"generatedAddress": "TXyz...abc123"
}
}payment.overpaidReceived amount was more than expected
Example Payload
{
"id": "evt_a1b2c3d4_1705078500000",
"type": "payment.overpaid",
"createdAt": "2025-01-12T15:05:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "overpaid",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"expectedAmount": "100.50",
"receivedAmount": "120.00",
"differenceAmount": "19.50",
"overpaidAmount": "19.50",
"alertType": "OVERPAID",
"txHash": "abc123def456...",
"generatedAddress": "TXyz...abc123"
}
}payment.expiredPayment expired without receiving funds
Example Payload
{
"id": "evt_a1b2c3d4_1705080600000",
"type": "payment.expired",
"createdAt": "2025-01-12T15:30:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "expired",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"generatedAddress": "TXyz...abc123",
"expiresAt": "2025-01-12T15:30:00.000Z"
}
}payment.failedPayment processing failed
Example Payload
{
"id": "evt_a1b2c3d4_1705079400000",
"type": "payment.failed",
"createdAt": "2025-01-12T15:10:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "failed",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"generatedAddress": "TXyz...abc123"
}
}payment.canceledPayment was canceled by merchant or system
Example Payload
{
"id": "evt_a1b2c3d4_1705079100000",
"type": "payment.canceled",
"createdAt": "2025-01-12T15:15:00.000Z",
"data": {
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "canceled",
"amount": "100.50",
"currency": "USDT",
"network": "TRON",
"tokenStandard": "TRC20",
"generatedAddress": "TXyz...abc123",
"generatedTokenAccount": null,
"addressMode": "PER_PAYMENT",
"merchantUserId": null,
"expectedAmount": "100.50",
"metadata": { "orderId": "1001" },
"createdAt": "2025-01-12T15:00:00.000Z",
"updatedAt": "2025-01-12T15:15:00.000Z",
"expiresAt": "2025-01-12T15:30:00.000Z"
}
}Retry Policy
If your endpoint returns a non-2xx status code or times out (30 second limit), we'll retry the webhook with exponential backoff:
After 5 failed attempts, the webhook will be marked as failed. You can view failed webhooks in your merchant dashboard.
Best Practices
- Always verify webhook signatures before processing
- Return 200 status code quickly, process asynchronously if needed
- Handle duplicate webhooks gracefully (idempotency)
- Log all webhook events for debugging and auditing
- Use HTTPS endpoints with valid SSL certificates