Skip to content

SDK Quickstart

A key, an install line, one call, and a payment page you can open.

The shortest path to a working payment is four steps. You create a key in the Dashboard, you put it in an environment variable, you install one package, and you make one call. What comes back is a URL. Open it and you are looking at a real Wajub payment page.

Everything else in this section builds on that call.

1. Get a key

Keys live in the Dashboard, under Settings, then Developer, then API keys. Every account has two sets from the first day, and the prefix is what tells them apart.

PrefixWhere it belongsWhat it can do
sk_test.Your server, sandboxEverything, on test money
sk.Your server, liveEverything, on real money
pk_test. / pk.The browserRead a session, nothing else
rk_test. / rk.A script or a CI jobOnly the scopes you grant it

A key is a prefix, a dot, then 96 characters. Server SDKs take the secret one.

2. Store it

Every server SDK reads WAJUB_API_KEY and WAJUB_WEBHOOK_SECRET from the environment on its own, so a .env file is usually the whole configuration step.

.env
WAJUB_API_KEY=sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…
WAJUB_WEBHOOK_SECRET=whsec_test_4f8c2b91d7e6a0f35c1dB7nY4hC6dF9j

Node.js is the one exception. It has no environment fallback, so you pass the key explicitly, as the example below does.

3. Install

One package per language, from the registry you already use.

LanguageInstallNeeds at least
Node.jsnpm install @wajub/nodeNode.js 18
Pythonpip install wajubPython 3.10
PHPcomposer require wajub/wajub-phpPHP 8.4
Gogo get github.com/wajubhq/wajub-goGo 1.22
Rubygem install wajubRuby 3.1
Javacom.wajub:wajub-java:1.1.1Java 17
C#dotnet add package Wajub.NET 8

4. Create a payment

This is the call. An amount, a currency, a way to reach the customer, and a URL to come back to.

curl https://api.wajub.com/payments \
  -H "Authorization: $WAJUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25000,
    "currency": "XAF",
    "email": "amina@example.com",
    "description": "Order 4172",
    "reference": "order-4172",
    "callback": "https://shop.example.com/complete"
  }'

Only amount and currency are truly required. Beyond those you need one way to identify the payer, which is email, phone or customer_id, any one of the three. callback is what makes the redirect flow work, and reference is your own order number coming back on every webhook.

5. Read what came back

The SDKs flatten the API envelope, so you get one object with the transaction fields and the two authorization fields together.

The fields that matter
{
"id": "trx_test_8kQ2mW9vB4nL6hR1cY3d",
"reference": "order-4172",
"amount": 25000,
"currency": "XAF",
"status": "pending",
"authorization_url": "https://pay.wajub.com/tok_xxxxx",
"authorization_token": "tok_xxxxx",
"sandbox": true
}
FieldWhat you do with it
authorization_urlSend the customer here, or open it yourself to test
authorization_tokenThe session token, if you embed the checkout instead of redirecting
idYour handle on the payment, for retrieve and for refunds. trx_test_ in sandbox, trx_ live
referenceYours, echoed back on the object and on every webhook
statuspending on creation. It is not the final word

Open authorization_url in a browser now. That page is the checkout, already carrying your account branding, and the sandbox accepts the test cards and numbers.

6. Confirm it server side

The redirect brings the customer back to your callback URL with a status parameter. Use it to pick the page you show them, never to release the goods.

curl https://api.wajub.com/payments/trx_test_8kQ2mW9vB4nL6hR1cY3d \
  -H "Authorization: $WAJUB_API_KEY"

The browser is not a reliable reporter. A customer can close the tab on a successful payment, and a Mobile Money confirmation can land minutes after the redirect. The webhook is the event that is guaranteed to arrive, so fulfilment belongs there and this call is the fallback.

What the SDK is doing for you

The same behaviour in all seven, and it is the reason to use a package rather than a raw HTTP call.

BehaviourDetail
IdempotencyAn Idempotency-Key is generated for every POST and PUT, so a replay never double charges
RetriesTwo automatic retries on 429 and 5xx, with exponential backoff and jitter. Node.js does three
Never a blind replayA POST is retried only because it carries an idempotency key
Timeouts30 seconds per request by default
ErrorsA typed exception per class of failure, not a status code to switch on
Paginationlist() returns a page you can iterate, and it fetches the next one for you

The API version is pinned on your account, not in the SDK

No SDK sends an X-Wajub-Version header. Your account is pinned at creation, new accounts get the current version, and you override it per request by sending that header yourself. Details on Versioning.

When the call fails

Every SDK raises a typed error rather than returning a status. The class is the decision: retry, fix the request, or tell the payer something.

HTTPClassWhat it means
401AuthenticationErrorWrong key, or a live key against sandbox data
403PermissionErrorThe key is valid but lacks the scope
404NotFoundErrorNo such payment, customer or account
400, 422InvalidRequestErrorMalformed request. errors names the field
429RateLimitErrorToo many requests. retry_after carries the wait
otherWajubErrorThe base class, also the one to catch broadly
noneApiConnectionErrorNetwork or timeout, no response at all

Two spellings differ from that list. PHP suffixes its subclasses with Exception, so the row above reads InvalidRequestException, while the base stays WajubError. Go capitalizes the initialism, so its connection class is APIConnectionError.

Whatever the class, four fields are always there: the message, a code, the HTTP status, and an errors map keyed by field name. That last one is what you show back on a form.

import { WajubError, WajubInvalidRequestError, WajubRateLimitError } from '@wajub/node';

try {
  await wajub.payments.create({ amount, currency: 'XAF', email });
} catch (error) {
  if (error instanceof WajubInvalidRequestError) {
    return res.status(422).json({ fields: error.errors });
  }
  if (error instanceof WajubRateLimitError) {
    return res.status(503).set('Retry-After', String(error.retryAfter ?? 5)).end();
  }
  if (error instanceof WajubError) {
    logger.error({ code: error.code, status: error.httpStatus });
  }
  throw error;
}

Ruby, Java and C#

The three of them do exactly what the five above do, with the same eighteen resources and the same call shape. Their pages carry the idiomatic version of every example here.

Where to go next

What did you think of this content?