Documentation Index
Fetch the complete documentation index at: https://docs.stablepay.global/llms.txt
Use this file to discover all available pages before exploring further.
StablePay sends webhooks to notify you of events in real-time.
Setup
- Configure your webhook URL in the Dashboard
- Set a webhook secret for signature verification
- Ensure your endpoint returns
200 OK quickly
All webhooks follow this structure:
{
"event": "transaction.payout_completed",
"timestamp": "2025-06-15T10:30:00.000Z",
"partnerId": "your_partner_id",
"data": {
// Event-specific payload
}
}
Payout Completed Example
{
"event": "transaction.payout_completed",
"timestamp": "2025-06-15T10:30:00.000Z",
"partnerId": "ss_api_partner_production",
"data": {
"transactionId": "txn_1768722777_abc123",
"merchant_id": "your_merchant_reference",
"payoutId": "NKSHV2beTTiymZY64S1DWMcxYDP8",
"orderId": "order_1768722781916_fa33d9127b22",
"amount": 50000,
"currency": "INR",
"status": "SUCCESS",
"utr": "601813168575",
"provider": "TSP-1",
"receiver_name": "JOHN DOE",
"bank_account_masked": "9240****5350",
"ifsc_code": "UTIB0005616",
"completedAt": "2025-06-15T10:30:00.000Z",
"message": "Payout completed successfully"
}
}
Event Types
User Events
| Event | Trigger |
|---|
user.created | User created via API |
user.kyc_updated | KYC status changed |
Transaction Events
| Event | Trigger |
|---|
transaction.created | Transaction created |
transaction.deposit_detected | Deposit seen on blockchain |
transaction.deposit_confirmed | Required confirmations met |
transaction.sweep_confirmed | Pool settlement sweep confirmed on-chain |
transaction.sweep_failed | Pool settlement sweep failed |
transaction.payout_initiated | Bank transfer started |
transaction.payout_completed | Payout successful |
transaction.payout_failed | Payout failed |
Signature Verification
Verify webhook signatures to ensure authenticity:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
// Parse: t=timestamp,v1=signature
const [timestampPart, signaturePart] = signature.split(',');
const timestamp = timestampPart.replace('t=', '');
const receivedSig = signaturePart.replace('v1=', '');
// Check timestamp (5 min tolerance)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
throw new Error('Webhook timestamp too old');
}
// Compute expected signature
const message = `${timestamp}.${payload}`;
const expectedSig = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
// Compare
if (!crypto.timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(receivedSig)
)) {
throw new Error('Invalid webhook signature');
}
return true;
}
// Express example
app.post('/webhooks/stablepay', (req, res) => {
const signature = req.headers['x-stablepay-signature'];
const payload = JSON.stringify(req.body);
try {
verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET);
// Process webhook
const { event, data } = req.body;
handleWebhookEvent(event, data);
res.status(200).json({ received: true });
} catch (error) {
res.status(401).json({ error: error.message });
}
});
Retry Policy
If your endpoint doesn’t return 200-299:
| Attempt | Delay |
|---|
| 1 | Immediate |
| 2 | 1 second |
| 3 | 2 seconds |
| 4 | 4 seconds |
| 5 | 8 seconds |
After 5 failures, the webhook is marked as failed.
Manual Retry
Retry failed webhooks via API:
curl -X POST https://api.stablepay.global/v2/webhooks/{eventId}/retry \
-H "Authorization: Bearer YOUR_API_KEY"
Best Practices
Return 200 immediately, then process async. We timeout after 30 seconds.
Webhooks may be delivered multiple times. Use eventId for deduplication.
Always verify webhook signatures in production.
Log webhook payloads for debugging and audit trails.
Testing Webhooks
Use the Dashboard to send test webhooks to your endpoint.