Skip to content

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.

Base URL (test & production)
https://api.wajub.com

Sandbox

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.

FeatureEndpointsSandbox
Payment links/linksLive only
Invoices/invoicesLive only
Tax/tax and /customers/{id}/tax_idsLive only
Shield/shieldLive only
Payments, refunds, transfers, customers, webhooksAvailable

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

TypePrefixWhere it runsWhat it can do
Publicpk. / pk_test.Browser, mobile appCreate a payment from the page. Holding it is not a permission: see the endpoints that accept it.
Privatesk. / sk_test.Your server onlyEverything: payments, refunds, transfers, balance.
Restrictedrk. / rk_test.Your server onlyOnly 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.

Key shape
sk_test.xxxxxxxxxxxxxxxx…
# prefix "sk_test", a dot, then the random secret

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.

.env
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

  1. Complete your business verification in the Dashboard. See Account activation & KYC for the required documents and the review timeline.
  2. Create production keys. Sandbox keys cannot be promoted, they are separate objects.
  3. Point your webhook endpoints to their production URLs and copy the new signing secret, it differs per environment.
  4. Re-test the features that were live only in the table above. This is the first time your code runs against them.
  5. Follow the Going Live guide for the full checklist.

What did you think of this content?