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.
- 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.
- 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.
- 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.
- 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.