Bridge API DocsBridge API Docs
Bridge API Docs

Callbacks

Set up and handle callbacks and webhooks

Callbacks

Callbacks (webhooks) allow you to receive real-time notifications about transaction statuses and other events. This enables you to build responsive applications that react immediately to payment updates.

Overview

When you initiate a payment or send an SMS, you can specify a callback_url where we'll send status updates. This eliminates the need to constantly poll our API for updates.

Setting Up Callbacks

1. Provide Callback URL

Include the callback_url parameter in your API requests:

{
  "service_id": 1,
  "reference": "Transfer to momo",
  "customer_number": "0541234567",
  "transaction_id": "79",
  "callback_url": "https://your-domain.com/webhook/payment-status"
}

2. Make Your Endpoint Accessible

Your callback URL must be:

  • Publicly accessible (not behind authentication)
  • Respond with HTTP 200 status
  • Handle POST requests
  • Respond within 30 seconds

3. Handle the Callback

Process the incoming callback data and respond appropriately.

Callback Format

All callbacks are sent as POST requests with JSON payloads:

{
  "transaction_id": "79",
  "status": "000",
  "status_desc": "Transaction Successful",
  "network_transaction_id": "57537797464",
  "message": "SUCCESSFUL",
  "amount": 0.1,
  "currency_code": "GHS",
  "customer_number": "0541234567",
  "reference": "Transfer to momo",
  "timestamp": "2025-08-31T23:45:00Z"
}

Callback Fields

FieldTypeDescription
transaction_idstringYour original transaction ID
statusstringTransaction status code
status_descstringHuman-readable status description
network_transaction_idstringNetwork's transaction ID
messagestringStatus message
amountnumberTransaction amount
currency_codestringCurrency code
customer_numberstringCustomer's phone number
referencestringYour reference
timestampstringISO 8601 timestamp

Status Codes

CodeDescriptionMeaning
000Transaction SuccessfulPayment completed successfully
001Transaction FailedPayment failed
002Transaction PendingPayment still processing
003Transaction CancelledPayment was cancelled

Implementation Examples

Node.js/Express

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/payment-status', (req, res) => {
  const { transaction_id, status, status_desc, amount } = req.body;
  
  console.log(`Transaction ${transaction_id}: ${status_desc}`);
  
  if (status === '000') {
    // Payment successful
    handleSuccessfulPayment(transaction_id, amount);
  } else if (status === '001') {
    // Payment failed
    handleFailedPayment(transaction_id, status_desc);
  }
  
  // Always respond with 200
  res.status(200).send('OK');
});

function handleSuccessfulPayment(transactionId, amount) {
  // Update your database
  // Send confirmation email
  // Update order status
  console.log(`Payment ${transactionId} successful: ${amount}`);
}

function handleFailedPayment(transactionId, reason) {
  // Log the failure
  // Notify customer
  // Update order status
  console.log(`Payment ${transactionId} failed: ${reason}`);
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Python/Flask

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route('/webhook/payment-status', methods=['POST'])
def payment_webhook():
    data = request.get_json()
    
    transaction_id = data.get('transaction_id')
    status = data.get('status')
    status_desc = data.get('status_desc')
    amount = data.get('amount')
    
    print(f"Transaction {transaction_id}: {status_desc}")
    
    if status == '000':
        handle_successful_payment(transaction_id, amount)
    elif status == '001':
        handle_failed_payment(transaction_id, status_desc)
    
    return jsonify({'status': 'received'}), 200

def handle_successful_payment(transaction_id, amount):
    # Update database
    # Send confirmation email
    # Update order status
    print(f"Payment {transaction_id} successful: {amount}")

def handle_failed_payment(transaction_id, reason):
    # Log failure
    # Notify customer
    # Update order status
    print(f"Payment {transaction_id} failed: {reason}")

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)

PHP

<?php
// webhook/payment-status.php

// Get the raw POST data
$input = file_get_contents('php://input');
$data = json_decode($input, true);

$transaction_id = $data['transaction_id'];
$status = $data['status'];
$status_desc = $data['status_desc'];
$amount = $data['amount'];

error_log("Transaction {$transaction_id}: {$status_desc}");

if ($status === '000') {
    handle_successful_payment($transaction_id, $amount);
} elseif ($status === '001') {
    handle_failed_payment($transaction_id, $status_desc);
}

// Always return 200
http_response_code(200);
echo json_encode(['status' => 'received']);

function handle_successful_payment($transaction_id, $amount) {
    // Update database
    // Send confirmation email
    // Update order status
    error_log("Payment {$transaction_id} successful: {$amount}");
}

function handle_failed_payment($transaction_id, $reason) {
    // Log failure
    // Notify customer
    // Update order status
    error_log("Payment {$transaction_id} failed: {$reason}");
}
?>

Security Considerations

1. Verify Callback Source

While callbacks come from our servers, you should implement additional verification:

app.post('/webhook/payment-status', (req, res) => {
  // Verify the request is from Bridge
  const signature = req.headers['x-bridge-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!verifySignature(payload, signature)) {
    return res.status(401).send('Unauthorized');
  }
  
  // Process the callback
  processCallback(req.body);
  res.status(200).send('OK');
});

2. Idempotency

Handle duplicate callbacks gracefully:

const processedTransactions = new Set();

app.post('/webhook/payment-status', (req, res) => {
  const { transaction_id } = req.body;
  
  // Check if already processed
  if (processedTransactions.has(transaction_id)) {
    return res.status(200).send('Already processed');
  }
  
  // Process the callback
  processCallback(req.body);
  processedTransactions.add(transaction_id);
  
  res.status(200).send('OK');
});

3. Timeout Handling

Implement proper timeout handling:

app.post('/webhook/payment-status', (req, res) => {
  // Set timeout
  req.setTimeout(25000); // 25 seconds
  
  try {
    processCallback(req.body);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Callback processing error:', error);
    res.status(500).send('Processing error');
  }
});

Testing Callbacks

1. Use ngrok for Local Development

# Install ngrok
npm install -g ngrok

# Expose your local server
ngrok http 3000

# Use the ngrok URL as your callback_url
# https://abc123.ngrok.io/webhook/payment-status

2. Test with Webhook.site

# Visit webhook.site to get a test URL
# Use the provided URL as your callback_url
curl -X POST "https://api.bridgeagw.com/make_payment" \
  -H "Authorization: Basic <your_credentials>" \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": 1,
    "callback_url": "https://webhook.site/your-unique-id",
    "transaction_id": "test-123"
  }'

3. Mock Callback for Testing

// Mock callback for testing
const mockCallback = {
  transaction_id: "test-123",
  status: "000",
  status_desc: "Transaction Successful",
  network_transaction_id: "57537797464",
  message: "SUCCESSFUL",
  amount: 0.1,
  currency_code: "GHS",
  customer_number: "0541234567",
  reference: "Test Payment",
  timestamp: new Date().toISOString()
};

// Test your callback handler
processCallback(mockCallback);

Retry Policy

If your callback endpoint doesn't respond with HTTP 200, we'll retry:

  • Retry Count: 3 attempts
  • Retry Interval: 5 minutes between attempts
  • Timeout: 30 seconds per attempt

After 3 failed attempts, the callback will be marked as failed and won't be retried.

Best Practices

  1. Always Respond with 200: Even if processing fails, respond with HTTP 200 to prevent retries
  2. Process Asynchronously: Don't block the callback response with heavy processing
  3. Log Everything: Log all callbacks for debugging and auditing
  4. Handle Duplicates: Implement idempotency to handle duplicate callbacks
  5. Validate Data: Always validate the callback data before processing
  6. Monitor Performance: Monitor your callback endpoint performance

Troubleshooting

Common Issues

  1. Callback Not Received: Check if your endpoint is accessible and responding with 200
  2. Timeout Errors: Ensure your endpoint responds within 30 seconds
  3. Duplicate Callbacks: Implement idempotency to handle duplicates
  4. Processing Errors: Log errors and handle them gracefully

Debugging

// Add comprehensive logging
app.post('/webhook/payment-status', (req, res) => {
  console.log('Callback received:', {
    headers: req.headers,
    body: req.body,
    timestamp: new Date().toISOString()
  });
  
  try {
    processCallback(req.body);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Callback processing error:', error);
    res.status(200).send('Error logged'); // Still return 200
  }
});