Quickstart
Your first payment end to end, from API key to verified webhook, in about five minutes.
This guide takes you from an empty account to a verified payment with a working webhook. You'll need a Wajub account (free) and a few minutes.
1. Create your account and get your keys
Create an account on the Dashboard. A new account holds no key, so
once logged in, open Settings → Developer → API Keys and create a private key while the
Dashboard shows the sandbox. It comes out as sk_test.….
| Key | Environment | Where it belongs |
|---|---|---|
pk_test.… | Sandbox | Public. Safe in a browser, initializes payments client-side. |
sk_test.… | Sandbox | Private. Server-side only, no real money moves. |
pk.… | Production | Public. Same client-side role, on real funds. |
sk.… | Production | Private. Server-side only, moves real money. |
Production keys only become available once your account is verified, and restricted rk keys
are covered in Environments & API keys.
Everything in this guide runs on your server, so everything here uses the private sandbox key. The public key exists for the one case where code runs in a browser, initializing a payment from the page itself. Never use a private key from a browser: when Wajub detects one, it refuses the request and emails you, so a leaked key does not stay silent.
2. Install the SDK for your language
The SDKs wrap the same HTTP API with typed resources, an Idempotency-Key on every mutating call,
a per-request timeout and webhook signature verification. If you would rather call the API
directly, every example below has a cURL tab.
npm install @wajub/nodeRuby, Java, C#, Kotlin, Flutter and React Native have their own SDKs and their own guides. See SDKs & Libraries for the full list and the configuration reference.
3. Create a payment
A payment starts with one request. You send the amount, the currency, who is paying, and the page
you want that person sent back to. Wajub creates the payment, keeps it pending and answers with a
hosted page where the customer picks their operator and confirms. Nobody is charged yet.
Describe the payer inside the customer object. The API also accepts a top-level email, and both
end up on the same customer record, but the object is the structure that 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.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XAF",
"customer": { "email": "[email protected]" },
"callback": "https://shop.example.com/order/complete"
}'Wajub answers 201 Created with the payment, its id, and the page to send the customer to:
Store transaction.id against your order, then redirect the customer to authorization_url.
Amounts are in the major unit, never in centimes
"amount": 5000 means 5 000 XAF, not 50 XAF. Wajub never uses subunits. Send 500 for 500 NGN
and 10.50 for 10.50 USD. See Key Concepts → Amounts.
4. Pay it with a sandbox number
Open authorization_url in your browser. In test mode the payment page accepts fake Mobile Money
numbers whose last 6 digits decide the outcome. The same suffixes work for every operator and
every country:
| Suffix | Result |
|---|---|
000000 | Payment succeeded |
000001 | Insufficient funds |
000002 | Failure, operator declined |
000003 | Timeout |
000004 | Pending → cancelled |
000009 | Payment succeeds, a later refund on it fails |
Pick a number for the operator you want to exercise:
| Country and operator | Prefix | Example, success |
|---|---|---|
+23767 | +237670000000 | |
+23769 | +237690000000 | |
+22505 | +225050000000 | |
+22507 | +225070000000 | |
+22503 | +225030000000 | |
+22177 | +221770000000 | |
+23324 | +233240000000 | |
+2348 | +2348000000000 | |
+25470 | +254700000000 | |
+25677 | +256770000000 |
The sandbox payment page asks for no PIN: the test number alone decides the outcome.
Sandbox offers payment methods that production may not
The sandbox payment page always offers a test card, so you can exercise that code path. The live payment page only offers the channels your active providers cover. A card appears there only once a provider that accepts cards is active on your account. Build and test against Mobile Money first.
5. Verify the payment server-side
The customer lands back on your callback URL, and that return proves nothing. Anyone can open
that URL by hand, and a customer who closes the tab never opens it at all. Before you ship an
order, ask the API what really happened, from your server, with the id you stored in step 3.
https://api.wajub.com/payments/{id}curl https://api.wajub.com/payments/trx_test_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: sk_test.xxxxxxxxxxxxxxxx…"The response carries the payment as Wajub knows it, whatever your browser was told:
Fulfill the order only when status is succeeded, and only after checking that amount and
currency match what you charged for. A payment that is still pending is not a failure, it is a
customer who has not finished yet, so leave the order open and let the webhook in step 6 tell you
when it lands.
Track the transaction in Konsole
Open Konsole → API Logs to see the exact request/response Wajub recorded. Once you are live, Routing Log shows which provider was chosen and why. It does not exist in the sandbox.
6. Set up a webhook endpoint
Polling the API after every redirect only covers the customers who come back. Webhooks cover the rest: a payment confirmed ten minutes later on the operator's side, a refund issued from the Dashboard, a dispute opened by the customer. Set one up now, before you build anything on top.
Register the endpoint
In Konsole → Webhooks → Endpoints (or Settings → Developer → Webhooks in the Dashboard):
- Click Add Endpoint.
- Enter your public URL, for example
https://yourapp.com/webhooks/wajub. - Select the events to receive. Start with every payment event, using Select all beside them.
A subscription matches exact names only, so typing
payment.*would receive nothing. - Save, then copy the Signing Secret (
whsec_…) into your environment asWAJUB_WEBHOOK_SECRET.
Give the SDK the signing secret
The client you already built in step 3 also verifies webhooks, once it knows the signing secret. Add it to the same constructor, next to your API key:
import { Wajub } from '@wajub/node';
export const wajub = new Wajub({
apiKey: process.env.WAJUB_API_KEY,
webhookSecret: process.env.WAJUB_WEBHOOK_SECRET,
});Write the endpoint
Four rules make a webhook endpoint correct, and the SDK only handles the first one for you.
Verify the signature, in every environment. An endpoint that skips verification accepts
anything anyone posts to it, including a forged payment.succeeded. constructEvent recomputes
the HMAC over X-Wajub-Timestamp and the raw body, compares it with X-Wajub-Signature, rejects
anything older than five minutes, and only then parses the JSON. It needs the raw body, so
disable JSON body parsing on this route.
Acknowledge before you work. Wajub waits 10 seconds for your 2xx. Past that the delivery
counts as failed and gets retried, so return first and do the slow work afterwards, in a queue or
a goroutine.
Deduplicate on the delivery id. A failed delivery is retried with a growing wait, for up to 72 hours,
and every attempt carries the same X-Wajub-Delivery-Id. That id is what
keeps a retry from shipping the same order twice.
Then act on the event. The type is in event.event, and event.data holds the object the
event is about, with the same shape the API returns for it. For payment.succeeded, event.data.id
is the payment id you stored in step 3.
app.post('/webhooks/wajub', express.raw({ type: '*/*' }), async (req, res) => {
const event = wajub.webhooks.constructEvent(
req.body,
req.headers['x-wajub-signature'],
req.headers['x-wajub-timestamp'],
);
res.sendStatus(200);
const deliveryId = req.headers['x-wajub-delivery-id'];
if (await alreadyHandled(deliveryId)) return;
await markHandled(deliveryId);
if (event.event === 'payment.succeeded') {
await fulfillOrder(event.data.id);
}
});Receive events on your machine
You do not need a public URL, a deployment or a registered endpoint to start. The CLI opens an outbound connection to Wajub and posts every sandbox event to a local address:
npm install -g @wajub/cli
wajub listen --forward-to http://localhost:3000/webhooks/wajubNothing on your machine is exposed to the internet. wajub listen prints the secret forwarded
events are signed with, and that is the secret your local WAJUB_WEBHOOK_SECRET needs while you
develop. It stays the same between runs, so set it once (wajub listen --print-secret prints it
alone); it is not a registered endpoint's secret, each endpoint keeps its own. See the
CLI listen command for all options.
Complete webhook guide
Signature verification, retry policy, event catalog, and idempotency are all covered in the Webhooks section.
What's next?
Related pages
- Key ConceptsPayment lifecycle, sandbox vs live, session tokens, orchestration.
- WebhooksSignature verification, event catalog, retry policy.
- Accept a payment, full guideInitialize, redirect, verify, and fulfill end-to-end.
- Go LiveThe complete checklist before switching to production.
- Best PracticesIdempotency, error handling, and security patterns for production.
- API ReferenceEvery endpoint with request/response examples.
Integration checklist
Sign up on the Wajub Dashboard and get your sandbox API keys.
View guide →Initialize a sandbox transaction with your public key.
View guide →Subscribe to payment.succeeded and payment.failed events.
View guide →Replace test keys with live keys and deploy to production.
View guide →