Direct API
Charge a payment from your server, on a channel you chose, with no Wajub page.
With the Direct API you render every screen. Your app collects the phone number, your server charges the payment on the channel you picked, and you show the waiting state and the result yourself. Wajub sends the prompt to the payer's handset and tells you the outcome.
It takes two calls. POST /payments records the payment, exactly as in every other mode, and
POST /payments/{id} charges it. The second call is the one this page covers.
This call belongs on your server
Send it with your secret key, from your backend. A request carrying an Origin or Referer header
with an sk.… key is rejected with a 403. If your payment screen lives in a browser, use
Embedded Components instead.
1. Create the payment
Create the payment as the Payments Quickstart shows.
Keep transaction.id from the answer. You will not use authorization_url or
authorization_token: those are for the hosted page and the components.
The payment is pending, and nobody has been charged yet.
2. Charge it
https://api.wajub.com/payments/{id}channelstringrequiredGET /channels, such as cm.mtn or cm.orange. Send mobile or a country aggregate such as cm.mobile to let Wajub find the operator from the phone number.data.phonestringoptionaldata.operatorstringoptionalcm.mobile only: the operator the payer picked, such as orange. Used when the number alone does not tell Wajub which operator it belongs to, a ported number for instance.data.ibanstringoptionaldata.account_number is accepted instead.data.wallet_addressstringoptionaldata.save_payment_methodbooleanoptionalfalse.Every field under data is also accepted at the root of the body. Send an Idempotency-Key header
unique to this charge, so that a retry after a timeout never charges twice.
curl https://api.wajub.com/payments/trx_test_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: charge-order-4172-1" \
-d '{
"channel": "cm.mtn",
"data": { "phone": "+237670000000" }
}'The answer
For Mobile Money the answer is almost always 202 Accepted. The prompt is on the payer's handset,
the payment has moved to processing, and message is the line to show while you wait:
The outcome arrives later, as a payment.succeeded or payment.failed webhook. Before you fulfil
the order, retrieve the payment and read its
status, as in every other mode.
| Code | What it means | What to do |
|---|---|---|
200 | The channel confirmed at once. The payment is succeeded | Verify, then fulfil |
202 | Waiting for the payer, or for a next step named in action | Show message, wait for the webhook |
400 | The payment can no longer be charged: it already succeeded, expired or was cancelled | Create a new payment if needed |
402 | The operator or the card issuer declined. The payment is back to pending | Let the payer try again, see below |
403 | Refused by a restriction on your account, or by Shield. A Shield block carries a BLK-… reference | Do not retry the same attempt |
404 | No payment with this id, in this environment | Check the id and the key |
422 | The channel is not accepted, the number does not match the operator, or the amount is above the channel's ceiling | Fix the request |
A 402 carries error_code, a stable reason such as insufficient_funds that you can translate for
the payer, and limit_exceeded when the payment would go past one of your account's limits.
When action is set
Mobile Money needs nothing more from you: action is absent and you wait for the webhook. Two other
values exist.
confirm_3dsmeans the card issuer wants to authenticate the payer. That step happens in the payment page or the components, which is one more reason to take cards there.confirmmeans the payer has to act on their side. For crypto,crypto.depositgives the address and the amount to send.
Letting Wajub find the operator
With channel set to mobile, or to a country aggregate such as cm.mobile, Wajub reads the
operator from the number and charges on it: +23767… goes to cm.mtn. When the number cannot be
attributed, a ported one for instance, Wajub uses data.operator if you sent it with a country
aggregate, and answers 422 otherwise.
With a precise channel such as cm.mtn, the number has to belong to that operator. An Orange number
sent on cm.mtn is refused with a 422 that names the right channel, instead of being charged on
the wrong network.
Retrying after a decline
A 402 leaves the payment pending, so the same payment can be charged again: with another number,
another operator, or the same one once the payer has topped up. Use a new Idempotency-Key for
that new attempt. Sending the first key again returns the first answer, the 402, without trying
again.
The same key is still the right one after a timeout or a network error, when you do not know whether the first call went through.
Cards
Live refuses raw card data: a card number or a security code in the body is answered with a 422
and error_code: raw_card_data_not_accepted, before anything is charged. Cards are collected by
Hosted Checkout, Embedded Components or a
mobile SDK, which pass them to the payment provider's own secure fields.
Sandbox accepts channel: card with the published test cards only, so you can exercise the whole
flow. Any other card number is refused there too.
In sandbox
The last six digits of the phone number decide the outcome, as on the hosted page. …000000
succeeds; …000001 is refused at once with a 422 on data.phone and the payment stays pending.
The full table is in Test scenarios.
The Direct API is available on every plan, in sandbox and in live. Splitting a payment into
instalments, with POST /payments/{id}/splits, works in sandbox only for
now.
Related pages