BluebulbAPI
    Using the API

    Webhooks Rolling out

    Orbita can POST to your server when something happens on your account, so you don't have to poll. Each delivery is signed so you can check it came from Orbita.

    Set up an endpoint

    • In the Orbita dashboard, go to Settings → Webhooks and enter an HTTPS URL for each event you want. Each event type has its own URL and its own signing secret.
    • The signing secret (whsec_…) is shown once, when you save the URL. Store it securely — you need it to verify deliveries.
    • The URL must be public HTTPS. Orbita doesn't follow redirects.

    Events

    virtual_account_funding
    A payment into one of your sub-wallets has been credited to your NGN balance.
    sub_wallet_provisioned
    A sub-wallet has been created and has an account number.

    What a delivery looks like

    A POST with a JSON body — the event's payload itself, with no envelope — and these headers:

    X-Orbita-Event
    The event type, e.g. virtual_account_funding.
    X-Orbita-Signature
    HMAC-SHA256 of the raw request body, keyed with that event's signing secret, hex-encoded.
    Content-Type
    application/json

    Verify the signature

    Compute the HMAC-SHA256 of the raw body bytes with the secret for the event in X-Orbita-Event, hex-encode it and compare it to X-Orbita-Signature in constant time. Reject the delivery if they differ. Verify before parsing — re-serialising parsed JSON won't reproduce the same bytes.

    // Express. Read the raw body -- verify it before parsing JSON.
    const crypto = require("crypto");
    const express = require("express");
    
    // One secret per event type, from Settings -> Webhooks.
    const SECRETS = {
      virtual_account_funding: process.env.ORBITA_FUNDING_WEBHOOK_SECRET, // "whsec_..."
      sub_wallet_provisioned: process.env.ORBITA_SUB_WALLET_WEBHOOK_SECRET,
    };
    
    const app = express();
    app.post("/orbita/webhooks", express.raw({ type: "application/json" }), (req, res) => {
      const secret = SECRETS[req.get("X-Orbita-Event")];
      if (!secret) return res.sendStatus(400);
    
      const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
      const received = req.get("X-Orbita-Signature") || "";
      const valid =
        received.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
      if (!valid) return res.sendStatus(401);
    
      const payload = JSON.parse(req.body.toString("utf8"));
      res.sendStatus(200); // respond fast, then process
      // Deliveries can repeat: skip payloads you've already handled.
    });
    Deliveries carry no timestamp or event ID. The signature proves a delivery came from Orbita, but not that it's new — a captured delivery can be sent again. Record what you've processed (e.g. the funding event's `reference`) and ignore repeats.

    Responding and retries

    • Return any 2xx status within 10 seconds. Anything else — another status, a timeout, a connection error — counts as a failed attempt.
    • Failed deliveries are retried about every 30 seconds, up to 6 attempts in total. After that, Orbita stops trying.
    • Because of retries, the same event can arrive more than once. Make your handler idempotent.
    • To have a payment's funding webhook sent again later, use Resend a payment webhook.

    virtual_account_funding

    Sent once a payment into a sub-wallet has been credited to your NGN balance. You can also fetch the payment with List payments received — its providerReference equals reference below.

    subWalletId
    ID of the sub-wallet paid into.
    customerReference
    The sub-wallet's customerReference, or null.
    accountNumber
    The sub-wallet's account number.
    amount
    Amount received (number).
    currency
    Always ngn.
    payer
    name, accountNumber and bankName of the account that paid. Any of them can be null when the bank doesn't provide it.
    reference
    The bank's reference for the payment. Unique per payment — use it to spot repeat deliveries.
    sessionId
    The bank transfer's session ID, or null.
    resultingWalletBalance
    Your NGN balance straight after this payment, as a string (e.g. "8525000").
    paidAt
    When Orbita credited the payment (ISO 8601, UTC).
    {
      "subWalletId": "c2d8e4f6-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
      "customerReference": "CUST-1042",
      "accountNumber": "9012345678",
      "amount": 150000,
      "currency": "ngn",
      "payer": {
        "name": "CHUKWUDI OKAFOR",
        "accountNumber": "0701234567",
        "bankName": "Access Bank"
      },
      "reference": "000013260923160248000123456789",
      "sessionId": "000013260923160248000123456789",
      "resultingWalletBalance": "8525000",
      "paidAt": "2026-09-23T15:02:48.920Z"
    }

    sub_wallet_provisioned

    Sent when Create a sub-wallet succeeds. The same details are in that request's response; this event is useful if the response was lost.

    subWalletId
    ID of the new sub-wallet.
    customerLabel
    The label you sent.
    customerReference
    The customerReference you sent, or null.
    accountNumber
    The account number your customer pays into.
    accountName
    Account name payers see.
    bankName
    Bank the account is held at.
    currency
    Always ngn.
    status
    The sub-wallet's status, normally active. See Statuses.
    {
      "subWalletId": "c2d8e4f6-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
      "customerLabel": "Ada Stores",
      "customerReference": "CUST-1042",
      "accountNumber": "9012345678",
      "accountName": "Ada Stores",
      "bankName": "Wema Bank",
      "currency": "ngn",
      "status": "active"
    }