Skip to main content
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.

Configure your webhook

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 attemptNext retry
11 minute
25 minutes
315 minutes
41 hour
54 hours
612 hours
After the seventh failed attempt, delivery is marked failed. Implement your webhook handler with these rules:
  1. Verify the HMAC signature.
  2. Deduplicate using X-Bumxpress-Webhook-Id or event id.
  3. Update your local record using data.id and data.type.
  4. Acknowledge with 200 OK only after the update succeeds.
  5. Fallback to GET /payins/{id}, GET /payouts/{id}, or GET /transfers/{id} when you need to recover from missed events.
Last modified on July 8, 2026