Quickstart
A sandbox payment from create to verified webhook, for an account that already has keys.
A payment is four steps, and they never change: create it, send the customer to a page, ask the API what really happened, then let webhooks tell you about everything that lands after the customer is gone. The four integration modes differ only in who renders the second step. The rest of this page is identical for all of them.
This page starts after setup
It assumes an account, sandbox keys and an installed SDK. If you have none of that yet, the platform quickstart covers account creation, keys and install, then walks the same four steps from zero.
1. Create the payment
One request carries the whole intent: how much, in which currency, who is paying, and where that
person comes back. Wajub records it, keeps it pending, and answers with a page to send the
customer to. Nothing has reached an operator yet, and nobody has been charged.
Send it from your server with the secret key. The public key creates payments too, but it exists for the one case where the call happens inside a browser.
Describe the payer in the customer object. A top-level email also works and lands on the same
customer record, but the object is what grows with you: it carries name, phone, address and
metadata as well.
https://api.wajub.com/paymentscurl https://api.wajub.com/payments \
-H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XAF",
"customer": { "email": "amina@example.com" },
"reference": "order-4172",
"callback": "https://shop.example.com/order/complete"
}'Wajub answers 201 Created. Two fields matter here: transaction.id, which you store against your
order, and authorization_url, which is where the customer goes next.
Amounts are in the major unit, never in centimes
"amount": 5000 is 5 000 XAF, not 50. Wajub has no subunits. A payment in XAF or XOF must fall
between 25 and 2 000 000; outside that range the API answers 422 with the bound in
errors.amount.
reference is yours: your order number, returned on every read and searchable in the Dashboard. It
carries no uniqueness constraint, so sending it twice creates two payments.
What does protect you from a double charge is the Idempotency-Key header. Generate it yourself,
store it next to the order, and resend the same value on every retry; the API then returns the
original payment instead of opening a second one, however long after, and refuses the key with a
422 if the amount, the currency or the reference changed. A key is spent for good: a new attempt
on the same order needs a new one. The SDKs do attach a key automatically, but a fresh random one
per call, which deduplicates nothing. Idempotency covers the strategies that work.
2. Send the customer to the page
Redirect to authorization_url. The customer picks an operator, types their number, confirms on
their handset and lands back on your callback.
That is Hosted Checkout, and it is the mode we recommend. Wajub owns the screen, the operator list, the retry when a prompt times out and the wording of every failure. You own none of it, and none of it is code you maintain.
The session stays valid for 24 hours. Pass expires.in in minutes, anywhere from 5 to 43 200, to
close the window sooner.
In sandbox, the last six digits of the number decide the outcome, and …000000 succeeds. The full
suffix table and a number per operator are in the platform
quickstart. Sandbox never asks for a
PIN.
3. Verify before you fulfil
The customer landing back on your callback proves nothing. Anyone can open that URL by hand, and a
customer who closes the tab never opens it at all. Ask the API what happened, from your server, with
the id you stored in step 1.
https://api.wajub.com/payments/{id}curl https://api.wajub.com/payments/trx_test_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…"The answer is the payment as Wajub knows it, whatever the browser was told. channel names the
operator that was actually used, and amount_paid is what was really collected:
Ship the order only when status is succeeded, and only once amount and currency match what
you charged for. A payment still pending is not a failure, it is a customer who has not finished
yet: leave the order open and let the next step close it.
4. Let the webhook close the loop
Verifying on return covers the customers who come back. Webhooks cover everyone else, and every event that has no browser behind it at all: a confirmation the operator sends ten minutes late, a refund issued from the Dashboard, a session that expires untouched.
Subscribe to the six events a payment can produce, by name. A subscription matches exact names
only, so payment.* would receive nothing.
| Event | Fires when |
|---|---|
payment.created | The payment was recorded and is pending |
payment.processing | The request reached the operator, the customer is being prompted |
payment.succeeded | Funds captured. This is the one to fulfil on |
payment.failed | The operator reported the attempt as failed after the prompt went out. A decline answered at once leaves the payment pending and sends nothing |
payment.expired | The session ran out before the customer confirmed |
payment.cancelled | Cancelled with DELETE /payments/{id}, or with the Cancel button of the hosted page |
Webhooks are documented once, not here
Endpoint registration, signature verification, the retry schedule and the full catalog live in
Webhooks. One rule belongs here though: deduplicate on
X-Wajub-Delivery-Id. A failed delivery is retried for up to 72 hours and every attempt carries
that same id, which is what keeps a retry from shipping the order twice.
Then pick the mode you ship
What you just built is Hosted Checkout. The other three modes change exactly one thing, who renders the screen in step 2. Creating, verifying and handling webhooks stay word for word what they are above.
Integration modes
- Hosted CheckoutWhat you just built. Wajub renders operator choice, prompt and result.
- Embedded ComponentsThe same screen, mounted in your page with the session token.
- Payment LinksNo integration at all. Production only, 25 links on the free plan.
- Direct APIYou collect the number and charge it yourself with a second call.
What's next?
Related pages
- Payment lifecycleEvery status a payment can hold, and the moves between them.
- Payment methods & channelsCountry and operator coverage, channel by channel.
- Phone number formatsWhat each operator accepts, before a payment can even start.
- RefundsFull or partial refunds on a succeeded payment.
- Accept a payment, full guideThe same flow end to end, with the fulfilment logic around it.
- Go LiveThe checklist before you switch to production keys.