Start here
How AfroPin works
The AfroPin API covers the whole life of a PIN: resellers issue and top up PINs, and brands redeem them at checkout. It is a JSON API over HTTPS, and every request is signed with HMAC.
Base URL
Endpoint paths in the API reference are relative to this URL. Test and live mode share it; the API key you sign with decides the mode. Environments →
What the API covers
As a brand you accept PINs issued on the network as payment.
Check the wallets your redemptions credit and browse the PIN products you accept, with your redemption fee.
Take payment with a PIN at checkout. Redeem the full balance or part of it, and attribute it to a shop.
List every redemption your account has credited, or look one up by its reference.
Register the shops where PINs are redeemed, and look up where your customers can buy them.
Receive a pin.spent event for every redemption you make with a webhook attached.
Requests
- Send request bodies as JSON with
Content-Type: application/json. - Every request is signed with your API key and carries a unique
Idempotency-Key. See Authentication. - IDs such as
pin_id,catalog_idandwebhook_idare UUIDs. - Write requests that move money accept an
external_reference: your own ID for the operation. A reference can only be used once, so it also protects you from charging twice. - You can send up to 60 requests per minute. Only one write request per account is processed at a time; a second one sent while the first is running fails with
rate_limited.
Responses
Every response is JSON with the same envelope. ok is true or false. A success puts the result in data (lists add paging); a failure puts code, message and fields in error.
200 Success
{
"ok": true,
"data": {
"transaction_ref": "3c6bb90c-4dc9-4e01-bdf2-75e91199847d",
"amount": 10,
"balance": 90,
"currency": "NGN",
"mode": "test",
"external_reference": "order_10293",
"processed_at": "2026-10-05T09:14:22.000000Z"
}
}
403 Error
{
"ok": false,
"error": {
"code": "pin_blocked",
"message": "This PIN is blocked",
"fields": null
}
}
- Errors carry a stable
error.code; validation errors also list each failing field inerror.fields. Branch onerror.code, never onerror.message, which is translated into your account language. Errors → - Amounts are JSON numbers in the currency's main unit, for example
10for 10 naira. - Timestamps are ISO 8601 in UTC.
- Objects carry a
modefield,testorlive.
Storing PIN codes
A PIN code is shown in full only once, at the moment it is created. We keep a one-way fingerprint of it (to recognise it when it is redeemed) and a masked version such as
2340-****-****-1001, never the code itself. A lost code can't be shown again by anyone, including our support team, but the value isn't lost: the reseller that sold the PIN can reissue it with a new code, for the same customer. The one exception is a webhook that carries a code and fails to deliver: that code is kept encrypted only so a resend can carry it, and is deleted as soon as a delivery succeeds or after 72 hours. Webhook logs always show codes as ****-****-****-****.- You never need to keep a customer's PIN code: send it to Take payment with a PIN and keep only
pin_maskfor your records. - Don't log or store the codes customers give you.
Products that issue a new code
Some products replace the PIN code after it is used, so a code that leaked earlier stops working. The product tells you which: see new_code_after_spend and new_code_after_top_up on Look up a product. When the code changes, the old code fails with pin_code_replaced and the new code is sent once:
new_pin_mask in the Take payment with a PIN response, when the product has new_code_after_spend. Only the mask: brands never receive the full code, so staff can't reuse a customer's PIN. Tell the customer their code has changed; they get the new one from the business they bought the PIN from.Getting started
-
Switch your dashboard to test mode
API keys and webhooks belong to the mode your dashboard is in when you create them.
-
Create a test API key
Go to API keys and generate a key. Copy the key ID and the secret; the secret is shown only once.
-
Sign a first request
Call GET /brand/wallets with the code in Authentication.
-
Add a webhook
Register an endpoint under Webhooks so your server hears about changes. Webhooks guide
-
Follow the integration guide, then pass UAT
The integration guide walks through the full flow in test mode. When it works, run the Go-live checklist to request live access.