Skip to main content

Authentication

The API uses the OAuth 2.0 client credentials grant. You exchange your client_id and client_secret for a JWT access token, then send that token on every request.

:::note Sandbox vs production Sandbox (https://sandbox.merchants.taifapay.africa) and production (https://merchants.taifapay.africa) each have their own client_id / client_secret — a sandbox token only works against the sandbox host, and vice versa. :::

This is your company token — it's what you use for every endpoint in these guides (transactions, checkout invoices, customers, wallet lookups). There's no separate customer-facing credential to issue yourself: customer authentication (SMS OTP) happens only on Taifa Pay's own hosted checkout page, never through this API — see Customers & wallets.

Obtaining a token​

POST /v1/auth/token

Credentials go in the HTTP Basic Authorization header — base64(client_id:client_secret) — and the body carries the grant type:

curl -X POST https://sandbox.merchants.taifapay.africa/v1/auth/token \
-u "your_client_id:your_client_secret" \
-H "Content-Type: application/json" \
-d '{"grant_type": "client_credentials"}'

Successful response:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"expires_in": "3599",
"token_type": "Bearer"
}
FieldMeaning
access_tokenJWT scoped to your company
expires_inLifetime in seconds (~1 hour)
token_typeAlways Bearer

A 401 Unauthorized means the credentials are wrong, the Authorization: Basic header is missing/malformed, or grant_type is not client_credentials.

Using the token​

Send the token on every API call, in either of two equivalent headers:

# Standard header (recommended)
-H "Authorization: Bearer eyJhbGciOiJIUzI1..."

# Alternative header, same format including the Bearer prefix
-H "x-auth-token: Bearer eyJhbGciOiJIUzI1..."

Token lifecycle​

  • Tokens live for about 1 hour (expires_in: 3599).
  • Request a new token before the old one expires and cache it; do not request a fresh token per API call.
  • A 401 on a normal endpoint usually just means the token expired — fetch a new one and retry once.

Example caching pattern (Node.js):

let cached = { token: null, expiresAt: 0 };

async function getToken() {
if (cached.token && Date.now() < cached.expiresAt - 60_000) {
return cached.token;
}
const res = await fetch('https://sandbox.merchants.taifapay.africa/v1/auth/token', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),
'Content-Type': 'application/json',
},
body: JSON.stringify({ grant_type: 'client_credentials' }),
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
cached = {
token: data.access_token,
expiresAt: Date.now() + Number(data.expires_in) * 1000,
};
return cached.token;
}

Keeping credentials safe​

  • Store client_secret in a secret manager or environment variable, never in source control.
  • Only call the API from your backend. The customer-facing checkout page needs no credentials — customers just open the checkoutUrl.
  • If a secret leaks, contact support to rotate it immediately.