Environments & API keys
Understand test and production modes, key formats and how to switch safely.
Wajub runs two isolated environments behind one API, with the same base URL, endpoints and payloads. The key you send decides which one you are talking to, so switching environments means swapping a credential, never rewriting code.
https://api.wajub.comSandbox
The sandbox is where you build. Mobile Money operators are simulated by test numbers whose last six digits decide the outcome, webhooks fire exactly as they do in production, and no money ever moves. It shares no data with live, so a sandbox customer, payment or event never appears in your live account.
Keys are pk_test.… and sk_test.…, and every object created there carries test_ in its id.
Production
Production moves real money. It opens once your business verification is approved, see Account activation & KYC for the documents and the timeline.
Keys are pk.… and sk.….
What the sandbox does not cover
The sandbox reproduces the payment flow faithfully, but five feature areas are live only. Calling
them with a test key returns 403 with This feature is only available in live mode.
| Feature | Endpoints | Sandbox |
|---|---|---|
| Payment links | /links | Live only |
| Invoices | /invoices | Live only |
| Tax | /tax and /customers/{id}/tax_ids | Live only |
| Shield | /shield | Live only |
| Payments, refunds, transfers, customers, webhooks | Available |
One more difference is the opposite way round: the sandbox payment page always offers a test card so you can exercise that code path, while the live 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 against Mobile Money first.
Key types
| Type | Prefix | Where it runs | What it can do |
|---|---|---|---|
| Public | pk. / pk_test. | Browser, mobile app | Create a payment from the page. Holding it is not a permission: see the endpoints that accept it. |
| Private | sk. / sk_test. | Your server only | Everything: payments, refunds, transfers, balance. |
| Restricted | rk. / rk_test. | Your server only | Only the scopes you grant it, one resource at a time. |
A key is a prefix, a dot, then a long random secret. Store keys as text, never in a fixed 32 or 64 character column.
sk_test.xxxxxxxxxxxxxxxx…
# prefix "sk_test", a dot, then the random secretNever commit a private key
Keep sk.… in environment variables or a secrets manager. If one leaks, revoke it from
Settings → Developers → API keys in the Dashboard. Rotating a key issues a new value immediately and the
old one stops working on the next request.
Narrowing what a key can do
Three controls exist on every key, and they cost nothing to turn on.
Scopes, on restricted keys. A restricted key holds an explicit list of
{resource}.{read|write} permissions: payment.read, payment.write, customer.read,
transfer.write, refund.write, recipient.*, invoice.*, dispute.*, webhook.*, event.*,
link.*, identity.*, tax.*, plus the read-only balance.read and account.read.
Anything outside the list returns 403. There is no balance.write, but transfer.write is
enough to send payouts with POST /transfers, so grant it only to a key whose job is to pay
people.
An IP allow-list. Each key accepts a list of exact IPv4 or IPv6 addresses, or CIDR ranges in
either family such as 203.0.113.0/24 or 2001:db8::/32. You enter it when you create the key,
and you can change it later, on every key type. A request from anywhere else is refused
with 403 IP address not allowed for this API key, whatever the key type. A key
that only works from your production servers is a key that is worthless in a leaked repository.
An expiry date. A key can carry expires_at. Past that moment it authenticates nothing. Use it
for a contractor, a migration script, or any integration you know is temporary. You set it when you
create the key, change or remove it later, on every key type, and read it next to the
key in the list: Expires …, or Expired … once the date has passed. A key with no expiry
shows no date.
Editing anything else about a key leaves its expiry as it was. A key created by approving a
pairing, wajub login or an app such as the WordPress plugin, expires after 90 days unless the
approver picked No expiry, which the approval page never offers for the CLI.
Environment variables
Three names cover a server integration. Keep them in the environment rather than in code, and name them exactly like this: the SDKs look for these spellings and no others.
WAJUB_API_KEY=sk_test.xxxxxxxxxxxxxxxx…
WAJUB_WEBHOOK_SECRET=whsec_test_xxxxxxxxxxxxxxxx
WAJUB_PUBLIC_KEY=pk_test.xxxxxxxxxxxxxxxx…WAJUB_API_KEY is the authentication credential. The Python and Go SDKs read it from the
environment on their own, so an empty configuration gives you a working client. The Node and PHP
SDKs do not, they throw on an empty configuration and expect you to pass the value, see
Setup your account for the exact
shape in each language.
WAJUB_WEBHOOK_SECRET is the one all four read automatically. Set it and signature verification
works without you passing the secret anywhere.
WAJUB_PUBLIC_KEY is read by no SDK. It is there for your own frontend code, and only if your
browser or mobile client initializes payments itself.
Production values belong in your secrets manager, never in a committed file. The safest setup keeps sandbox and production in separate variable sets, so promoting code never means editing a key by hand.
Going to production
- Complete your business verification in the Dashboard. See Account activation & KYC for the required documents and the review timeline.
- Create production keys. Sandbox keys cannot be promoted, they are separate objects.
- Point your webhook endpoints to their production URLs and copy the new signing secret, it differs per environment.
- Re-test the features that were live only in the table above. This is the first time your code runs against them.
- Follow the Going Live guide for the full checklist.