Bridge API DocsBridge API Docs
Bridge API Docs

Callbacks

Receive real-time transaction status updates via webhooks, including optional signed callbacks

Callbacks

When a transaction is completed, Bridge sends a POST request to the callback_url you provided in the original payment request. This eliminates the need to poll our API for status updates.

Callback URL Requirements

Your callback endpoint must:

  • Be publicly accessible
  • Accept POST requests with a JSON body
  • Respond with HTTP 200

Sample Callback Payload

{
  "collection_trans_id": null,
  "trans_ref": "2220260609162313-03947541",
  "payment_option": null,
  "trans_id": "0982989097",
  "trans_status": "000",
  "customer_number": "233244000001",
  "nw": "MTN",
  "payment_mode": "MOM",
  "amount": "0.21",
  "message": "SUCCESSFUL"
}

Callback Fields

FieldDescription
collection_trans_idBridge's internal transaction ID
trans_refYour original transaction ID (the transaction_id you submitted)
payment_optionPayment option — not in use, can be ignored
trans_idThe network's transaction ID
trans_statusTransaction status code
customer_numberCustomer's phone number
nwNetwork of the customer's phone number (e.g. MTN, VOD, AIR)
payment_modePayment mode used (e.g. MOM for Mobile Money)
amountTransaction amount
messageHuman-readable status description

Verifying callback signatures

Bridge can sign outbound callbacks (payment results and other async events) so your server can confirm the POST came from us and that the JSON body was not altered in transit.

When signature headers are present

TrafficSigned?
Live/production callbacksOnly after TheBridge enables callback signatures for your service and an active secret_key exists. Until then, live callbacks are unsigned — contact TheBridge to enable signing.
Sandbox payment callbacksAlways when an active secret_key exists. Use sandbox payment traffic to implement verification before going live.
Other sandbox callbacks (e.g. auto-debit forward)Same rule as live (follows the enrollment flag).

If signing is expected but no secret is configured, we still POST the callback without signature headers.

Headers

HeaderDescription
X-TheBridge-TimestampUnix time in seconds (UTC) when the signature was computed.
X-TheBridge-Signaturev1= followed by lowercase hex HMAC-SHA256.

Secret key

Use the same secret_key (API password) you use for Basic authentication (client_key / secret_key from your service keys). We never send the secret on the callback request. If you rotate or reset your keys, the signing secret changes immediately — update your verifier when you rotate.

Signed payload

The HMAC input is a single string:

{timestamp}.{raw_json_body}
  • {timestamp} is the exact value of X-TheBridge-Timestamp (ASCII digits).
  • {raw_json_body} is the raw HTTP request body as received (byte-for-byte). Do not parse JSON and re-serialize before verifying; key order and spacing must match what we sent.

Algorithm: HMAC-SHA256, output as lowercase hexadecimal, prefixed with v1= in the header.

Replay protection

Reject callbacks whose X-TheBridge-Timestamp is too old or too far in the future. We recommend a 5 minute tolerance window on your server clock.

Compare the received signature to the expected value in constant time. If verification fails, do not update the transaction.

Example implementation

const crypto = require("crypto");

function verifyTheBridgeCallback(rawBody, timestamp, signatureHeader, secretKey, maxSkewSec = 300) {
  if (!timestamp || !signatureHeader) return false;
  const now = Math.floor(Date.now() / 1000);
  const ts = Number(timestamp);
  if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewSec) return false;

  const expected =
    "v1=" +
    crypto.createHmac("sha256", secretKey).update(`${timestamp}.${rawBody}`).digest("hex");

  const received = Buffer.from(signatureHeader);
  const expectedBuf = Buffer.from(expected);
  if (received.length !== expectedBuf.length) return false;

  return crypto.timingSafeEqual(received, expectedBuf);
}
import hmac
import hashlib
import time

def verify_thebridge_callback(
    raw_body: bytes,
    timestamp: str | None,
    signature_header: str | None,
    secret_key: str,
    max_skew_sec: int = 300,
) -> bool:
    if not timestamp or not signature_header:
        return False
    try:
        ts = int(timestamp)
    except ValueError:
        return False
    now = int(time.time())
    if abs(now - ts) > max_skew_sec:
        return False
    expected = (
        "v1="
        + hmac.new(
            secret_key.encode(),
            f"{timestamp}.".encode() + raw_body,
            hashlib.sha256,
        ).hexdigest()
    )
    return hmac.compare_digest(signature_header, expected)

Handling the Callback

When signature headers are present, verify the callback before you update the transaction. Then use trans_status to determine the outcome and update your system accordingly.

trans_statusMeaningRecommended Action
000SuccessfulMark the transaction as successful and fulfil the order
001FailedMark the transaction as failed and notify the customer
002PendingTransaction is still processing — wait for a follow-up callback
003CancelledMark the transaction as cancelled

Use trans_ref to match the callback to the original transaction in your system.

For a full list of status codes, see Response Codes.

Missed Callbacks

If your system does not receive a callback (e.g. due to downtime or a network issue), you can query the transaction status directly using the Get Transaction Status endpoint. Pass the original transaction_id you submitted, and the response will include a status field that you should handle the same way as trans_status in the callback.