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/paymentsPayments platform
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.
Secured by IdealBox
Methods we carry
How it works
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.
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/paymentsWe 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.typeThe provider's callback and our own reconciler converge on one answer. A timeout stays open rather than being guessed as a failure.
payment_intent.succeededFunds land in a wallet, clear after the settlement delay, then withdraw to a bank or mobile money account on your schedule.
POST /v1/payoutsYour money
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.
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
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
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.
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
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.
Send the same key twice and you get the stored response back, not a second charge. Retries after a timeout are safe by construction.
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.
Every movement is a balanced journal entry, checked before it commits. Balances are derived from postings, never from a counter someone increments.
Who did what, when and from where — chained, so a tampered entry breaks the chain. Verified nightly.
Secret, publishable and restricted keys, each carrying its environment in the prefix. Per-key IP allowlists and rate limits.
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
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.