IdealBoxPay

Documentation

Take your first payment.

Everything below is the real contract: the routes, the statuses, the failure codes, and the rules a correct integration follows.

Get a test key

Everything in test mode runs against the simulator, which implements the same adapter contract as a real provider and is selected by the same routing rules. The path you exercise here is the path that runs in production.

  1. 01

    Create an account and copy your test keys from the dashboard. The environment is part of the key itself, so a test key cannot move real money.

  2. 02

    Create a payment with an amount in minor units, a currency, a country and a method type. You get an id back and nothing has been charged yet.

  3. 03

    Confirm it. That is the call that routes the payment to a provider and starts it, and the response tells you what the customer has to do next.

  4. 04

    Point a webhook endpoint at your server and verify the signature on every request. Fulfil the order when the succeeded event arrives, and only then.

Create a payment

The amount is a decimal string of minor units — "500000" in XAF is five hundred thousand francs, because XAF has no minor unit. The country decides the corridor, and therefore which providers are even eligible.

curl https://api.idealbox.org/v1/payment_intents \
  -H "Authorization: Bearer ib_sk_test_4f2a9c1e8b7d6053" \
  -H "Idempotency-Key: 9f2c1e44-7b21-4c0e-93a1-2f7c8d5e1a0b" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "500000",
    "currency": "XAF",
    "country": "CM",
    "methodType": "MOBILE_MONEY",
    "reference": "order-4471",
    "providerParams": { "phoneNumber": "+237699000000" }
  }'

Confirm it

Creating a payment records the instruction. Confirming it routes to a provider, creates a charge and asks that provider to start. The response carries the resolved provider, the flow kind, and a next action when the customer has something to do.

curl -X POST \
  https://api.idealbox.org/v1/payment_intents/pi_01JAY2K5RQ9M3W7ZB8XF4TC6VD/confirm \
  -H "Authorization: Bearer ib_sk_test_4f2a9c1e8b7d6053"

Listen for the outcome

Register your endpoint under Developers → Webhooks. Delivery is at-least-once, retried eight times over roughly three days, and signed with a rotating secret.

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);
  });

Go live

Swap the test key for a live one. Nothing else in your integration changes — not a URL, not a field name, not a status value.