Webhooks let Bumxpress push status changes to your backend instead of relying only on polling GET endpoints. Use them to update your internal records when payins, payouts, or transfers complete or fail.
In the merchant dashboard you configure:
- URL: HTTPS endpoint on your backend, for example
https://api.yourapp.com/webhooks/bumxpress
- Signing secret: Shared secret used to verify signatures
All webhook requests use:
POST /webhooks/bumxpress
Content-Type: application/json
X-Bumxpress-Signature: sha256=<hex_hmac>
X-Bumxpress-Timestamp: 1779705120
X-Bumxpress-Event: payout.completed
X-Bumxpress-Webhook-Id: 88888888-8888-8888-8888-888888888888
Verify the signature
Signatures use an HMAC over the timestamp and raw request body:
sha256=HMAC_SHA256(webhook_secret, timestamp + "." + raw_request_body)
Verify signatures before parsing JSON or applying side effects.
import crypto from "node:crypto";
export function verifyBumxpressWebhook({
secret,
timestamp,
rawBody,
signature,
}: {
secret: string;
timestamp: string;
rawBody: string;
signature: string;
}) {
const expected =
"sha256=" +
crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const expectedBuffer = Buffer.from(expected);
const signatureBuffer = Buffer.from(signature);
return (
expectedBuffer.length === signatureBuffer.length &&
crypto.timingSafeEqual(expectedBuffer, signatureBuffer)
);
}
Always verify X-Bumxpress-Signature using the raw request body. Reject requests when verification fails.
Event types
Bumxpress emits a small set of payment events. Use the event and data.type fields to route handling logic.
{
"id": "88888888-8888-8888-8888-888888888888",
"event": "payin.completed",
"created_at": "2026-06-08T10:40:00Z",
"data": {
"type": "payin",
"id": "44444444-4444-4444-4444-444444444444",
"object": { }
}
}
Supported events:
- payin.completed / payin.failed (
data.type: "payin")
- payout.completed / payout.failed (
data.type: "payout")
- transfer.payin.completed / transfer.payin.failed (
data.type: "transfer")
- transfer.payout.completed / transfer.payout.failed (
data.type: "transfer")
- transfer.completed / transfer.failed (
data.type: "transfer")
Transfer leg events are sent as each leg reaches a terminal state. Aggregate transfer events are still sent when the full transfer reaches a final state. The data.object field contains a snapshot of the resource at event time, including amounts, status, operator, wallet impact, and timestamps.
Example payloads
Payin webhook
{
"id": "88888888-8888-8888-8888-888888888888",
"event": "payin.completed",
"created_at": "2026-06-08T10:40:00Z",
"data": {
"type": "payin",
"id": "44444444-4444-4444-4444-444444444444",
"object": {
"id": "44444444-4444-4444-4444-444444444444",
"status": "completed",
"send_amount": "10000",
"send_currency": "XAF",
"receive_amount": "16",
"receive_currency": "USD",
"country_code": "CM",
"operator_code": "CM_MTN_MOMO",
"rail_type": "mobile_money",
"wallet_impact": {
"credit_amount": "16.00",
"currency": "USD"
},
"completed_at": "2026-06-08T10:39:30Z"
}
}
}
Payout webhook
{
"id": "99999999-9999-9999-9999-999999999999",
"event": "payout.completed",
"created_at": "2026-06-08T10:42:00Z",
"data": {
"type": "payout",
"id": "66666666-6666-6666-6666-666666666666",
"object": {
"id": "66666666-6666-6666-6666-666666666666",
"status": "completed",
"send_amount": "25",
"send_currency": "USD",
"receive_amount": "15125",
"receive_currency": "XAF",
"operator_code": "CM_MTN_MOMO",
"wallet_impact": {
"debit_amount": "26.50",
"currency": "USD"
},
"completed_at": "2026-06-08T10:41:30Z"
}
}
}
Transfer webhook
{
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"event": "transfer.completed",
"created_at": "2026-06-08T11:30:00Z",
"data": {
"type": "transfer",
"id": "77777777-7777-7777-7777-777777777777",
"object": {
"id": "77777777-7777-7777-7777-777777777777",
"status": "completed",
"payin_status": "completed",
"payout_status": "completed",
"send_amount": "100000",
"send_currency": "XAF",
"receive_amount": "98500",
"receive_currency": "XOF",
"corridor_code": "CM-CI-XAF-XOF",
"payer_operator_code": "CM_MTN_MOMO",
"recipient_operator_code": "CI_ORANGE_MOMO",
"collection_completed_at": "2026-06-08T11:15:00Z",
"disbursement_completed_at": "2026-06-08T11:28:00Z"
}
}
}
Transfer payin leg webhook
{
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"event": "transfer.payin.completed",
"created_at": "2026-06-08T11:15:05Z",
"data": {
"type": "transfer",
"id": "77777777-7777-7777-7777-777777777777",
"object": {
"id": "77777777-7777-7777-7777-777777777777",
"status": "disbursing",
"payin_status": "completed",
"payout_status": "pending",
"payin_amount": "100000",
"payin_currency": "XAF",
"payout_amount": "98500",
"payout_currency": "XOF",
"payer_operator_code": "CM_MTN_MOMO",
"recipient_operator_code": "CI_ORANGE_MOMO",
"collection_reference": "TXN_COLLECTION_123",
"collection_completed_at": "2026-06-08T11:15:00Z"
}
}
}
Transfer payout leg webhook
{
"id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"event": "transfer.payout.failed",
"created_at": "2026-06-08T11:28:05Z",
"data": {
"type": "transfer",
"id": "77777777-7777-7777-7777-777777777777",
"object": {
"id": "77777777-7777-7777-7777-777777777777",
"status": "failed",
"payin_status": "completed",
"payout_status": "failed",
"payin_amount": "100000",
"payin_currency": "XAF",
"payout_amount": "98500",
"payout_currency": "XOF",
"payer_operator_code": "CM_MTN_MOMO",
"recipient_operator_code": "CI_ORANGE_MOMO",
"disbursement_reference": "TXN_PAYOUT_123",
"disbursement_failed_at": "2026-06-08T11:28:00Z"
}
}
}
Delivery and retries
Return any 2xx status code to acknowledge receipt. If your endpoint returns a non-2xx or times out, Bumxpress retries with exponential backoff:
| Failed attempt | Next retry |
|---|
| 1 | 1 minute |
| 2 | 5 minutes |
| 3 | 15 minutes |
| 4 | 1 hour |
| 5 | 4 hours |
| 6 | 12 hours |
After the seventh failed attempt, delivery is marked failed.
Recommended handler pattern
Implement your webhook handler with these rules:
- Verify the HMAC signature.
- Deduplicate using
X-Bumxpress-Webhook-Id or event id.
- Update your local record using
data.id and data.type.
- Acknowledge with
200 OK only after the update succeeds.
- Fallback to
GET /payins/{id}, GET /payouts/{id}, or GET /transfers/{id} when you need to recover from missed events.