Webhooks
Receive real-time notifications about payment events
Webhooks
Webhooks allow you to receive real-time notifications about payment events. Instead of polling our API, you can register webhook endpoints to be notified when specific events occur.
Webhook functionality is being enhanced. Core features are available, advanced features coming soon.
Overview
How Webhooks Work
- Event Occurs: A payment is completed, refunded, or fails
- Webhook Triggered: Moneybag sends HTTP POST to your endpoint
- You Respond: Your server processes and responds with 200 OK
- Retry if Failed: Automatic retries with exponential backoff
Key Features
- Real-time notifications for all payment events
- Automatic retries with exponential backoff
- Signature verification for security
- Event ordering guaranteed per transaction
- Idempotency support to prevent duplicate processing
Setting Up Webhooks
1. Register Webhook Endpoint
Via Dashboard
- Log into your Merchant Dashboard
- Navigate to Developer Settings → Webhooks
- Click Add Webhook Endpoint
- Enter your endpoint URL and select events
Via API
curl -X POST https://api.moneybag.com.bd/api/v2/webhooks \
-H "X-Merchant-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yoursite.com/webhook",
"events": ["payment.success", "payment.failed"],
"description": "Production webhook endpoint"
}'
2. Webhook Requirements
Your webhook endpoint must:
- Accept HTTP POST requests
- Return 200 OK status within 30 seconds
- Be publicly accessible (not localhost)
- Use HTTPS in production
- Handle duplicate events idempotently
Webhook Events
Payment Events
Event | Description | When Triggered |
---|---|---|
payment.initiated | Payment process started | Customer redirected to payment page |
payment.processing | Payment being processed | Payment submitted for processing |
payment.success | Payment completed successfully | Funds received |
payment.failed | Payment failed | Payment declined or error |
payment.cancelled | Payment cancelled | Customer cancelled payment |
payment.expired | Payment session expired | 30 minutes timeout reached |
Refund Events
Event | Description | When Triggered |
---|---|---|
refund.initiated | Refund requested | Refund API called |
refund.processing | Refund being processed | Refund approved for processing |
refund.completed | Refund successful | Funds returned to customer |
refund.failed | Refund failed | Refund could not be processed |
Dispute Events
Event | Description | When Triggered |
---|---|---|
dispute.created | Dispute/chargeback initiated | Customer disputes transaction |
dispute.updated | Dispute status changed | New evidence or status update |
dispute.resolved | Dispute resolved | Final decision made |
Account Events
Event | Description | When Triggered |
---|---|---|
account.updated | Account settings changed | Profile or settings modified |
account.suspended | Account suspended | Violation or security issue |
account.activated | Account activated | Account approved or reactivated |
Webhook Payload
Standard Payload Structure
{
"id": "evt_1234567890",
"event": "payment.success",
"created_at": "2024-12-18T10:30:00Z",
"data": {
"transaction_id": "TXN_ABC123",
"order_id": "ORD_XYZ789",
"amount": 1000.00,
"currency": "BDT",
"status": "completed",
"payment_method": "card",
"customer": {
"name": "John Doe",
"email": "john@example.com",
"phone": "+8801700000000"
},
"metadata": {
"custom_field": "value"
}
}
}
Event-Specific Payloads
Payment Success
{
"id": "evt_payment_001",
"event": "payment.success",
"created_at": "2024-12-18T10:30:00Z",
"data": {
"transaction_id": "TXN_123",
"order_id": "ORD_456",
"amount": 500.00,
"currency": "BDT",
"status": "completed",
"payment_method": "bkash",
"paid_at": "2024-12-18T10:29:50Z",
"fees": 10.00,
"net_amount": 490.00
}
}
Refund Completed
{
"id": "evt_refund_001",
"event": "refund.completed",
"created_at": "2024-12-18T15:00:00Z",
"data": {
"refund_id": "REF_789",
"transaction_id": "TXN_123",
"refund_amount": 200.00,
"original_amount": 500.00,
"refund_reason": "Customer request",
"refunded_at": "2024-12-18T14:59:30Z"
}
}
Webhook Security
Signature Verification
All webhooks include a signature header for verification:
X-Webhook-Signature: sha256=3b4c5d6e7f8a9b0c1d2e3f4g5h6i7j8k9l0m1n2o3p4q5r6s7t8u9v0w1x2y3z4
Verification Code Examples
PHP
function verifyWebhookSignature($payload, $signature, $secret) {
$calculated = hash_hmac('sha256', $payload, $secret);
return hash_equals('sha256=' . $calculated, $signature);
}
// In your webhook handler
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
$secret = 'your_webhook_secret';
if (!verifyWebhookSignature($payload, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const calculated = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === `sha256=${calculated}`;
}
// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
res.status(200).send('OK');
});
Python
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
calculated = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return signature == f"sha256={calculated}"
# In your webhook handler
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
# Process webhook
return 'OK', 200
Handling Webhooks
Best Practices
-
Respond Quickly
// Good: Respond immediately, process async app.post('/webhook', (req, res) => { res.status(200).send('OK'); processWebhookAsync(req.body); // Process in background });
-
Idempotency
// Track processed events to prevent duplicates const processedEvents = new Set(); function handleWebhook(event) { if (processedEvents.has(event.id)) { return; // Already processed } // Process event processedEvents.add(event.id); }
-
Error Handling
app.post('/webhook', async (req, res) => { try { await processWebhook(req.body); res.status(200).send('OK'); } catch (error) { console.error('Webhook error:', error); res.status(500).send('Internal error'); } });
Retry Mechanism
Failed webhooks are retried with exponential backoff:
Attempt | Delay | Total Time |
---|---|---|
1 | Immediate | 0 seconds |
2 | 1 minute | 1 minute |
3 | 5 minutes | 6 minutes |
4 | 30 minutes | 36 minutes |
5 | 2 hours | 2.5 hours |
6 | 6 hours | 8.5 hours |
7 | 12 hours | 20.5 hours |
8 | 24 hours | 44.5 hours |
After 8 failed attempts, the webhook is marked as failed and won't be retried.
Testing Webhooks
Using Webhook Testing Tools
-
ngrok - Expose local server to internet
ngrok http 3000 # Use the generated URL as webhook endpoint
-
Webhook.site - Instant webhook testing
- Visit webhook.site
- Get unique URL
- Use as webhook endpoint
Sandbox Webhook Testing
Trigger test webhooks in sandbox:
curl -X POST https://sandbox.api.moneybag.com.bd/api/v2/webhooks/test \
-H "X-Merchant-API-Key: YOUR_SANDBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "payment.success",
"url": "https://yoursite.com/webhook"
}'
Test Event Triggers
In sandbox, use these amounts to trigger specific events:
Amount | Triggered Event |
---|---|
100.00 | payment.success |
200.00 | payment.failed |
300.00 | payment.cancelled |
400.00 | refund.completed |
Webhook Management API
List Webhooks
GET /api/v2/webhooks
Get Webhook Details
GET /api/v2/webhooks/{webhook_id}
Update Webhook
PUT /api/v2/webhooks/{webhook_id}
Delete Webhook
DELETE /api/v2/webhooks/{webhook_id}
Get Webhook Logs
GET /api/v2/webhooks/{webhook_id}/logs
Troubleshooting
Common Issues
Webhooks Not Received
- Verify endpoint is publicly accessible
- Check firewall rules
- Ensure HTTPS certificate is valid
- Verify correct events are subscribed
Signature Verification Failing
- Ensure using raw request body
- Check secret key is correct
- Verify no encoding/decoding issues
- Ensure proper HMAC algorithm (SHA256)
Duplicate Events
- Implement idempotency using event ID
- Track processed events in database
- Handle race conditions properly
Timeout Errors
- Respond immediately with 200 OK
- Process webhook asynchronously
- Optimize database queries
- Use message queues for heavy processing
IP Whitelisting
For additional security, whitelist Moneybag webhook IPs:
Production IPs:
103.XXX.XXX.XXX/24
203.XXX.XXX.XXX/24
Sandbox IPs:
103.XXX.XXX.XXX/24
Contact support for the complete IP list.