Webhooks API
Receive real-time notifications for payment events
Webhooks
Webhooks allow you to receive real-time notifications when events occur in your ShadhinPay account.
How Webhooks Work
- You provide a
callback_urlwhen creating a payment - When the payment status changes, we send a POST request to your URL
- Your server processes the event and returns a
200response
Event Types
| Event | Description |
|---|---|
payment.initiated | Payment was created |
payment.processing | Customer started payment |
payment.completed | Payment successful |
payment.failed | Payment failed |
payment.cancelled | Payment cancelled |
payment.expired | Payment link expired |
payment.refunded | Payment was refunded |
invoice.paid | Invoice was paid |
invoice.overdue | Invoice became overdue |
Webhook Payload
All webhooks include these fields:
{
"event": "payment.completed",
"timestamp": "2024-01-01T12:05:00Z",
"data": {
"payment_id": "SP_1234567890",
"status": "COMPLETED",
"amount": 1500,
"currency": "BDT",
"transaction_id": "TXN_ABC123",
"payment_method": "bkash",
"customer_phone": "8801712345678",
"metadata": {
"order_id": "ORD-12345"
}
},
"signature": "sha256=abc123..."
}Verifying Signatures
Important: Always verify webhook signatures before processing events to prevent fraud.
We sign all webhooks using HMAC-SHA256. Here's how to verify:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secretKey) {
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(JSON.stringify(payload))
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-shadhinpay-signature'];
const isValid = verifyWebhookSignature(req.body, signature, SECRET_KEY);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const { event, data } = req.body;
switch (event) {
case 'payment.completed':
// Fulfill the order
break;
case 'payment.failed':
// Handle failure
break;
}
res.status(200).send('OK');
});import hmac
import hashlib
import json
def verify_webhook_signature(payload, signature, secret_key):
expected = hmac.new(
secret_key.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature
# In your webhook handler
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-ShadhinPay-Signature')
if not verify_webhook_signature(request.json, signature, SECRET_KEY):
return 'Invalid signature', 401
event = request.json['event']
data = request.json['data']
if event == 'payment.completed':
# Fulfill the order
pass
return 'OK', 200Retry Policy
If your webhook endpoint returns an error (non-2xx status), we'll retry:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, we mark the webhook as failed and stop retrying.
Best Practices
- Return 200 quickly - Process webhooks asynchronously
- Verify signatures - Always validate before processing
- Handle duplicates - Use
payment_idfor idempotency - Log everything - Store webhook payloads for debugging
- Use HTTPS - We only send webhooks to secure endpoints
Testing Webhooks
Use tools like ngrok to test webhooks locally:
# Start ngrok tunnel
ngrok http 3000
# Use the ngrok URL as your callback_url
# https://abc123.ngrok.io/webhook