Webhook
Webhooks
Receive real-time notifications when transactions occur on your monitored addresses. Webhooks are HTTP callbacks that notify your application when events occur. Instead of polling our API, we push updates directly to your server.
Event Types
| Event | Description |
|---|---|
| transaction.detected | Transaction seen in mempool (0 confirmations) |
| transaction.confirmed | Transaction confirmed (configurable threshold) |
| address.created | New address generated |
| wallet.updated | Wallet settings changed |
Setting Up Webhooks
-
Create Webhook Endpoint
Create a POST endpoint on your server to receive webhooks:
app.post('/webhooks/sanpay', (req, res) => { const event = req.body; switch (event.type) { case 'transaction.confirmed': handleConfirmedTransaction(event.data); break; case 'transaction.detected': handlePendingTransaction(event.data); break; } res.status(200).send('OK'); });@app.route('/webhooks/sanpay', methods=['POST']) def handle_webhook(): event = request.json if event['type'] == 'transaction.confirmed': handle_confirmed_transaction(event['data']) elif event['type'] == 'transaction.detected': handle_pending_transaction(event['data']) return 'OK', 200 -
Register Webhook
Register your endpoint with SanPay:
curl -X POST https://api.sanpay.io/v1/webhooks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/sanpay", "events": ["transaction.detected", "transaction.confirmed"], "secret": "your-webhook-secret" }' -
Verify Signatures
Verify webhook authenticity using the signature header:
const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } app.post('/webhooks/sanpay', (req, res) => { const signature = req.headers['x-sanpay-signature']; const isValid = verifyWebhook( JSON.stringify(req.body), signature, process.env.WEBHOOK_SECRET ); if (!isValid) { return res.status(401).send('Invalid signature'); } // Process webhook... });
Webhook Payload
Transaction Confirmed
{
"id": "evt_abc123",
"type": "transaction.confirmed",
"timestamp": "2024-01-15T10:45:00Z",
"data": {
"id": "tx_def456",
"txHash": "abc123def456789...",
"walletId": "wal_abc123",
"address": "bc1qxy2kgdygjrsqvzq3rj...",
"amount": "0.00150000",
"direction": "incoming",
"confirmations": 6,
"blockHeight": 823456,
"metadata": {
"orderId": "order_12345"
}
}
} Transaction Detected
{
"id": "evt_xyz789",
"type": "transaction.detected",
"timestamp": "2024-01-15T10:40:00Z",
"data": {
"id": "tx_def456",
"txHash": "abc123def456789...",
"walletId": "wal_abc123",
"address": "bc1qxy2kgdygjrsqvzq3rj...",
"amount": "0.00150000",
"direction": "incoming",
"confirmations": 0,
"status": "pending"
}
} Retry Policy
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 24 hours |
- Your endpoint returns a non-2xx status code
- The request times out (30 second limit)
- The connection fails
Best Practices
- Respond quickly – Return 200 immediately, process asynchronously
- Handle duplicates – Use the event `id` to deduplicate
- Verify signatures – Always validate the `x-sanpay-signature` header
- Use HTTPS – We only deliver webhooks to secure endpoints
- Monitor failures – Set up alerts for failed deliveries