Bright SecureTechDeveloper documentation
API referenceTestingDashboard
Getting startedAPI overviewAuthenticationCheckout sessionsPaymentsWebhooksErrorsIntegration examplesTesting

API reference

Webhooks

Signed notifications to your server when money moves.

Add your endpoint in Settings → API keys & webhooks (separate URLs for test and live). We POST JSON to it and sign every request. Your signing secret (whsec_…) is shown once and can be rolled.

EventWhen
payment.successThe bank confirmed a payment.
payment.failedA payment was declined or timed out.
payout.successMoney was sent to your MoMo wallet.
payout.failedA payout failed; the money is back in your balance.
testSent from the dashboard to check your endpoint.
Request body
{
  "id": "evt_5b1c0e7a9d2f4e6a8c3b1d0e",
  "event": "payment.success",
  "created_at": "2026-11-12T14:30:41.220Z",
  "data": {
    "reference": "BST-4F9A21C0D7E3",
    "status": "successful",
    "amount_minor": 12000,
    "currency": "GHS",
    "method": "mtn_momo",
    "merchant_reference": "ORDER-10482",
    "failure_reason": null
  }
}

Verify the signature

Header: BST-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.raw_body" with your signing secret>. Recompute it over the raw body, compare in constant time, and reject anything older than 5 minutes.

import crypto from "node:crypto";

// Use the RAW request body: parsing and re-serialising JSON changes the bytes.
export function verifyBstSignature(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes: reject
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && crypto.timingSafeEqual(given, want);
}

Delivery and retries

  • Answer with any 2xx within 10 seconds. Do slow work after responding.
  • Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed. You can resend from the dashboard.
  • The same event can arrive more than once: use the event id to ignore duplicates.
  • Endpoints must be public https:// URLs on port 443. Redirects are not followed.
PreviousPaymentsNext Errors