Hosted Checkout
The page Wajub renders for your customer, what it handles, and what you control.
Hosted Checkout is the shortest path to a working payment. You create the payment, send the customer to the page Wajub renders, and verify the outcome when they come back. The operator list, the prompt, the retry when it times out and the wording of every failure belong to us, and they keep improving without you touching anything.
This page is the behaviour, not the code
What the hosted page does, what it decides on its own, and what you can change about it. The integration code itself, which URL to build and which component to mount for a redirect, an inline frame or an overlay, is in Wajub Components → Hosted checkout.
Create the payment
Everything the page needs comes from the create call. Send it from your server with the secret key: the public key also creates payments, but it exists for the case where the call is made in a browser.
https://api.wajub.com/paymentsamountnumberrequired10.50 is ten dollars fifty. In XAF and XOF the payment must fall between 25 and 2 000 000.currencystringrequiredcustomerobjectoptionalemail, phone, name, plus address and metadata. At least one of customer, email, phone or customer_id is required, and the object is the form that grows with you.descriptionstringoptionalitemsarrayoptionalname, a description and a quantity. They are shown, never summed: amount remains what you charge.callbackstring (url)optionalstatus in the query string. Must be HTTPS, and http://localhost or http://127.0.0.1 are accepted in sandbox; any other address answers 422. Omit it and they stay on a Wajub confirmation page, in which case the webhook is your only signal.referencestringoptionalexpires.inintegeroptionalbearerstringoptionalmerchant by default, or customer, which adds the fee on top and shows the breakdown on the page.acceptobjectoptionalcountries, channels, types and currencies. Omit it and every method you can collect on is shown. See [Choosing what a payment accepts](/payments/payment-methods#choosing-what-a-payment-accepts).metadataobjectoptionalcallback for the return: one address for every outcome.A create call that uses the hosted page's own fields looks like this:
curl https://api.wajub.com/payments \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{
"amount": 25000,
"currency": "XAF",
"customer": { "email": "[email protected]", "name": "Amina N." },
"description": "Order #4172",
"reference": "order-4172",
"items": [
{ "name": "Wireless earbuds", "quantity": 1 },
{ "name": "Delivery", "quantity": 1 }
],
"expires": { "in": 60 },
"callback": "https://shop.example.com/order/complete"
}'The response carries the payment and the page to send the customer to. Store transaction.id
against your order before you redirect:
authorization_url carries an opaque session token, not the transaction id. Redirect to it exactly
as returned: never parse it, and never build one yourself. The host is not always pay.wajub.com
either, since an account on the Scale plan can serve its checkout from its own verified domain.
What the page decides on its own
The customer sees the operators that can actually take their money, not a fixed list. Wajub filters by currency, by environment and by the amount, so a channel that cannot process XAF is never offered rather than failing at the last step.
Mobile Money caps a single debit at 500 000 XAF, and the same in XOF. In live a payment above
that is refused, and the page asks the payer for a smaller amount or another method. In sandbox the
page splits it into successive tranches and collects them one after another. That is the situation
where a payment sits in partial, described in the payment lifecycle.
While the operator prompt is out, the page shows the instruction that matches the channel, the
#150# style dialling code for one operator and the in-app confirmation for another. If you set
bearer to customer, it also shows the fee on top of your amount and the total actually being
debited.
The language is English or French. It follows the Settings → Regional → Language setting, which
accepts auto, en or fr. On auto, the page uses the customer's own preferred_language, and
opens in French when the customer has none.
Handle the return
Once the attempt is over, the customer comes back to your callback with the payment identified in
the query string:
https://shop.example.com/order/complete?reference=trx_test_CSUGajfv9xh0XQ5wu2lx&status=succeeded&trxref=order-4172reference is the Wajub payment id and trxref is the reference you sent, present only when you
set one. Both come back whatever the outcome: the return after a failed payment used to carry
status alone. status is succeeded, failed, cancelled or expired. After a success the page
sends the customer on by itself; after a failure or an expiry, when they choose to come back. A
customer who closes the page without finishing never comes back at all.
The return proves nothing
Anyone can open that URL by hand and change status. A customer who closes the tab never opens it
at all. Confirm with GET /payments/{id} from your server, or act on the payment.succeeded
webhook, before you ship anything.
The verification call takes the id you stored before redirecting:
curl https://api.wajub.com/payments/trx_test_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…"Check the amount and the currency as well as the status, and let the webhook close the orders whose customer never came back.
Make the page yours
The look of the page is set once for the whole account, in Settings → Branding, and applies to every payment page you ever create, in sandbox as in production.
| What you set | Where it shows |
|---|---|
| Rectangular logo, square logo, favicon | The header of the page and the browser tab |
| Primary, secondary and background colours | Buttons, links and the page surface |
| Text and button text colours | Contrast against your own palette |
| Success and error colours | The confirmation and the failure states |
| Font and border radius | The whole page |
| Appearance mode | system, light or dark |
| Density and cover style | How tight the layout is, and how the header is framed |
Branding needs a paid plan
Settings → Branding needs the Branding feature, which starts at Growth. On Pay as you go the payment page keeps the default appearance. Serving the checkout from your own domain is a separate feature, Custom domain, that starts at Scale.
You can also collect information the payment itself does not carry, a delivery note or a table
number, by adding fields in Settings → Collect → Fields. Each field has a type among text,
textarea, select, checkbox, number, email and tel, a label, an optional placeholder, and
a required flag. They appear on the hosted page in the order you arrange them.
What the customer types in those fields is not saved on the payment today, so it never comes back on
a read or in a webhook. When you need the answer, ask for it on your own site before creating the
payment, and send it in metadata.
Two things that look customizable and are not
The theming object accepted by POST /payments is stored on the payment and returned on reads,
but the hosted page never reads it: appearance comes from the account, not from the call. And
there is no locale or channels body parameter, so passing either has no effect on what the
customer sees.
Related pages
- Payments QuickstartThe same flow end to end, from create to verified webhook.
- Hosted checkout integrationRedirect, inline frame or overlay, with the code for each.
- BrandingEvery appearance setting and how it reaches the page.
- Payment methods & channelsWhich operators are offered, country by country.
- Payment lifecycleEvery state the payment holds while the customer is on the page.
- Test the payment pageSandbox numbers that drive each outcome on demand.