AfroPin Developers
Postman Sign in Get test keys

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

https://afropin.com/api/v1

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 reseller you buy PIN value from the network and resell it to your customers.

Balance and products

Check your wallet balances and browse the PIN products you are allowed to issue, with their denominations and your commission.

Issue PINs

Sell a PIN to your customer. Your wallet is debited and you receive a Luhn-valid PIN code to hand over.

Top-ups and lifecycle

Add value to an existing PIN, disable or re-activate it, and change the webhook that receives its events.

Locations

Register the shops where you sell PINs, and look up where your customers can redeem them.

Webhooks

Hear about every change to your PINs: issued, topped up, redeemed and expired.

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_id and webhook_id are 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 in error.fields. Branch on error.code, never on error.message, which is translated into your account language. Errors →
  • Amounts are JSON numbers in the currency's main unit, for example 10 for 10 naira.
  • Timestamps are ISO 8601 in UTC.
  • Objects carry a mode field, test or live.

Storing PIN codes

We never store 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 ****-****-****-****.
  • Save pin_code from Issue a PIN before you show it to your customer. If your system fails after the call, the code cannot be fetched again: the PIN still exists, and only pin_mask can be looked up.
  • Store codes encrypted at rest, keep them out of logs, analytics and error reports, and limit who can read them.
  • Deliver the code to your customer in a way they can find again, such as email or SMS with the receipt.

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:

After a top-up
new_pin_code in the Top up a PIN response, when the product has new_code_after_top_up.
After a brand redemption
pin_code in the pin.spent webhook to the PIN's webhook, when the product has new_code_after_spend. Webhooks

Replace the stored code with the new one each time, and give it to your customer. To receive codes changed by brand redemptions, attach a webhook_id when you issue the PIN, or add one later with Change the webhook on a PIN. You are the only one who receives a code changed by a redemption: brands are sent just its mask. Without a webhook (or the customer delivery you set when issuing), that new code reaches nobody and the PIN can't be used again.

Getting started

  1. Switch your dashboard to test mode

    API keys and webhooks belong to the mode your dashboard is in when you create them.

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

  3. Sign a first request

    Call GET /reseller/wallets with the code in Authentication.

  4. Add a webhook

    Register an endpoint under Webhooks so your server hears about changes. Webhooks guide

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

Questions, or found something that does not match these docs? Email [email protected].