Moneybag

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.

Implementation In Progress

Webhook functionality is being enhanced. Core features are available, advanced features coming soon.


Overview

How Webhooks Work

  1. Event Occurs: A payment is completed, refunded, or fails
  2. Webhook Triggered: Moneybag sends HTTP POST to your endpoint
  3. You Respond: Your server processes and responds with 200 OK
  4. 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

  1. Log into your Merchant Dashboard
  2. Navigate to Developer SettingsWebhooks
  3. Click Add Webhook Endpoint
  4. 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

EventDescriptionWhen Triggered
payment.initiatedPayment process startedCustomer redirected to payment page
payment.processingPayment being processedPayment submitted for processing
payment.successPayment completed successfullyFunds received
payment.failedPayment failedPayment declined or error
payment.cancelledPayment cancelledCustomer cancelled payment
payment.expiredPayment session expired30 minutes timeout reached

Refund Events

EventDescriptionWhen Triggered
refund.initiatedRefund requestedRefund API called
refund.processingRefund being processedRefund approved for processing
refund.completedRefund successfulFunds returned to customer
refund.failedRefund failedRefund could not be processed

Dispute Events

EventDescriptionWhen Triggered
dispute.createdDispute/chargeback initiatedCustomer disputes transaction
dispute.updatedDispute status changedNew evidence or status update
dispute.resolvedDispute resolvedFinal decision made

Account Events

EventDescriptionWhen Triggered
account.updatedAccount settings changedProfile or settings modified
account.suspendedAccount suspendedViolation or security issue
account.activatedAccount activatedAccount 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

  1. Respond Quickly

    // Good: Respond immediately, process async
    app.post('/webhook', (req, res) => {
        res.status(200).send('OK');
        processWebhookAsync(req.body); // Process in background
    });
  2. 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);
    }
  3. 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:

AttemptDelayTotal Time
1Immediate0 seconds
21 minute1 minute
35 minutes6 minutes
430 minutes36 minutes
52 hours2.5 hours
66 hours8.5 hours
712 hours20.5 hours
824 hours44.5 hours

After 8 failed attempts, the webhook is marked as failed and won't be retried.


Testing Webhooks

Using Webhook Testing Tools

  1. ngrok - Expose local server to internet

    ngrok http 3000
    # Use the generated URL as webhook endpoint
  2. Webhook.site - Instant webhook testing

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:

AmountTriggered Event
100.00payment.success
200.00payment.failed
300.00payment.cancelled
400.00refund.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.