IdealBoxPay

Payments platform

Every rail. One integration.

MTN MoMo, Orange Money, Visa, Apple Pay — one request creates the payment, one webhook tells you it succeeded, and one ledger explains where every franc went.

Live in a day. No card required to try the test environment.

IdealBox Checkoutorder-4471
Amount dueFCFA 5,000
  • MTN MoMo
  • Orange Money
  • Visa · Mastercard · Amex
  • Bank transfer
+237 6 99 00 00 00
Pay FCFA 5,000

Secured by IdealBox

Methods we carry

See coverage by country
Countries covered
12+
Payment methods
12
Settlement currencies
8
Typical settlement
T+1

How it works

The same four steps, whichever rail the customer picks.

A mobile money push and a 3-D Secure redirect are the same state machine with a different next action. That is the whole reason adding a provider does not cost you a release.

01

Create the payment

One request, whatever the rail. Name an amount, a currency and a method — or leave the provider out and let routing pick from what works in that country.

POST /v1/payments
02

The customer acts

We answer with a next action: approve on the handset, follow a redirect, submit a code. Your checkout branches on that, never on the provider's name.

nextAction.type
03

We resolve it

The provider's callback and our own reconciler converge on one answer. A timeout stays open rather than being guessed as a failure.

payment_intent.succeeded
04

You get paid

Funds land in a wallet, clear after the settlement delay, then withdraw to a bank or mobile money account on your schedule.

POST /v1/payouts

Your money

Know to the hour when funds become yours.

Captured charges are held for a settlement delay before they can be withdrawn. We show you every one of them, and exactly how far through the hold it is.

Clearing today

Illustrative
  • ch_01JAY2K5RQFCFA 1,250,000
    now
  • ch_01JAY2H8ZBFCFA 87,500
    in 6h
  • ch_01JAY2F1MCFCFA 425,000
    in 19h
  • ch_01JAY2C7XDFCFA 150,000
    in 32h
Total clearingFCFA 1,912,500
Journal entryje_01JAY2K5RQ · XAF
Debit
PROCESSOR_RECEIVABLE
5,000
Credit
WALLET_PENDING
4,875
Credit
PLATFORM_FEE_REVENUE
125
Debits equal credits5,000 = 5,000

Behind each of those rows is a balanced journal entry, written in the same database transaction that marked the charge succeeded. Nothing outside the ledger service can write a posting, and no endpoint that returns a balance is ever cached.

For developers

One call to charge. One handler to fulfil.

The response tells you what the customer has to do next. The webhook tells you it worked. Nothing else is required to be correct.

const response = await fetch(
  'https://api.idealbox.org/v1/payment_intents',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.IDEALBOX_SECRET_KEY}`,
      'Idempotency-Key': crypto.randomUUID(),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: '500000',        // minor units, as a string
      currency: 'XAF',
      country: 'CM',           // decides the corridor and the route
      methodType: 'MOBILE_MONEY',
      reference: 'order-4471',
      providerParams: { phoneNumber: '+237699000000' },
    }),
  },
);

const intent = await response.json();
// REQUIRES_CONFIRMATION. Nothing has been charged yet — confirm to start it.
console.log(intent.id, intent.status);
import { createHmac, timingSafeEqual } from 'node:crypto';

// The raw bytes, not the parsed body. Express's JSON parser re-serialises,
// and the re-serialised bytes will not match the HMAC we computed.
app.post('/webhooks/idealbox', express.raw({ type: 'application/json' }),
  (req, res) => {
    if (!verify(req.body, req.get('IdealBox-Signature'))) {
      return res.sendStatus(400);
    }

    const event = JSON.parse(req.body.toString('utf8'));

    // Delivery is at-least-once and can arrive out of order. Deduplicate on
    // the id, and compare against your own state rather than assuming order.
    if (alreadyProcessed(event.id)) return res.sendStatus(200);

    if (event.type === 'payment_intent.succeeded') {
      fulfil(event.data.object.reference);   // <- fulfil HERE
    }

    res.sendStatus(200);
  });

Webhooks

Delivered at least once, signed every time.

Events are written to an outbox in the same transaction as the thing that caused them, then delivered with retries and exponential backoff. A failing endpoint is retried, not forgotten.

Delivery logwe_01JAY7BQ · POST
  1. 19:07:00payment_intent.created2001
  2. 19:07:00payment_intent.requires_action2002
  3. 19:07:24payment_intent.processing2003
  4. 19:07:31payment_intent.succeeded5004
  5. 19:07:36payment_intent.succeededattempt 22005

Every request carries an IdealBox-Signature header, an HMAC over the timestamp and the raw body. Verify it before you trust anything inside.

Built for money

The parts you only notice when they are missing.

None of this is a premium add-on. It is the floor a payments platform has to stand on before it is allowed near a merchant's balance.

Idempotency keys

Send the same key twice and you get the stored response back, not a second charge. Retries after a timeout are safe by construction.

Signed webhooks

HMAC over timestamp and body, secret rotation without downtime, a guard against pointing an endpoint at our own network, and automatic disable after sustained failure.

Double-entry ledger

Every movement is a balanced journal entry, checked before it commits. Balances are derived from postings, never from a counter someone increments.

Hash-chained audit log

Who did what, when and from where — chained, so a tampered entry breaks the chain. Verified nightly.

Scoped API keys

Secret, publishable and restricted keys, each carrying its environment in the prefix. Per-key IP allowlists and rate limits.

Risk before money moves

Velocity checks, allow and deny lists, amount ceilings and duplicate detection run before a provider is ever called. Every decision is recorded with the ruleset version that made it.

Questions

The ones that come up first.

Start in the test environment. Move money when you are ready.

Create an account, take a test key and run a payment end to end in minutes. Going live is a different key, not a different integration.

No setup fee. No monthly minimum on Start.