AfroPin Developers
Postman Sign in Get test keys

Build your integration

Sell your own gift cards

Sell your own gift cards from your website, app or systems: the same calls resellers use, at your /brand/ paths. This guide sells a gift card in test mode and follows it through its life. To accept gift cards at your checkout, see the Take gift cards at checkout.

Before you begin

  • Your account officer has set up at least one of your products for selling, and selling is switched on for your account. Without it, selling calls fail with catalog_item_not_found or issuing_disabled.
  • You have a test API key from API keys. The same key works for redeeming and selling.
  • Your test issuing balance holds funds in the product's currency.
Selling your own gift cards differs from a reseller selling them: you pay the face value from your issuing balance and earn no commission, and when they are spent at your stores the lower redemption fee agreed for your own cards applies. Cash-outs are not available to brands through the API.

Sign your requests

Requests are signed exactly as for redemption; see Authentication. The examples below use this shell helper. Paste it into your terminal with your test keys, then run each step as written.

export BASE_URL="https://afropin.com/api/v1"
export KEY_ID="YOUR_TEST_PUBLIC_KEY"
export SECRET="YOUR_TEST_SECRET_KEY"

# api METHOD ENDPOINT [JSON_BODY]: signs the request and sends it with a fresh nonce and Idempotency-Key
api() {
  local method="$1" endpoint="$2" body="${3:-}"
  local ts nonce url_path body_hash signature
  ts=$(date +%s)
  nonce=$(openssl rand -hex 16)
  url_path=$(printf '%s' "$BASE_URL/$endpoint" | sed -E 's#^https?://[^/]+/##; s#\?.*$##')
  body_hash=$(printf '%s' "$body" | openssl dgst -sha256 -hex | sed 's/^.* //')
  signature=$(printf '%s' "$method.$url_path.$ts.$nonce.$body_hash" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
  local data=()
  [ -n "$body" ] && data=(--data-raw "$body")
  curl -sS -X "$method" "$BASE_URL/$endpoint" \
    -H "X-Key-Id: $KEY_ID" \
    -H "X-Timestamp: $ts" \
    -H "X-Nonce: $nonce" \
    -H "X-Signature: $signature" \
    -H "Idempotency-Key: $(openssl rand -hex 16)" \
    -H "Content-Type: application/json" \
    "${data[@]}"
}
Your external_reference is what stops a sale being made twice: reusing it fails with duplicate_external_reference. If a sale times out, look for your reference before retrying with a new Idempotency-Key.

Sell and manage a gift card

  1. Check your issuing balance

    Start with a read-only call to prove your signing works. Each wallet's amount is your issuing balance in that currency: selling or topping up one of your gift cards draws its face value from it. Fund it from your dashboard, or move money across from your redemption balance. If your test wallet is empty, contact support to fund it.

    GET /api/v1/brand/wallets

    Request

    api GET brand/wallets

    Response

    {
        "ok": true,
        "data": [
            {
                "amount": 250000,
                "currency": "NGN"
            }
        ]
    }
  2. Pick a product you sell

    These are your own gift cards that our team has set up for you to sell. Note the id: you pass it as catalog_id when selling. It also gives the amounts you can sell (any amount from min to max for any_amount, or one of the denominations for set_amounts), how many days a gift card lasts (duration) and whether it can be topped up. sale_commission_percent is always 0 on your own products: you sell at face value.

    GET /api/v1/brand/issuing-catalog

    Request

    api GET "brand/issuing-catalog?limit=20"

    Response (key fields)

    {
        "ok": true,
        "data": [
            {
                "id": "3d7e9a1b-2c4f-4e6a-8b0d-9f1e2a3b4c5d",
                "pin_type_id": "c2b9e1d4-5f6a-47b8-9c0d-1e2f3a4b5c6d",
                "name": "Lekki Bistro Gift Card",
                "currency": "NGN",
                "min": 5000,
                "max": 50000,
                "amount_rule": "set_amounts",
                "denominations": [5000, 10000, 25000, 50000],
                "duration": 365,
                "can_top_up": false,
                "spend_rule": "partial",
                "new_code_after_top_up": false,
                "new_code_after_spend": true,
                "sale_commission_percent": 0
            }
        ],
        "paging": {
            "page": 1,
            "pages": 1,
            "per_page": 20,
            "total": 1,
            "next": null,
            "previous": null
        }
    }
    A product you only accept, and don't sell, is not listed here. Ask your account officer to set one up for selling.
  3. Sell a gift card

    Sell a gift card to your customer. Its face value (amount) is taken from your issuing balance; there is no commission. The customer is emailed their gift card with a QR code for your till. Pass store_id (one of your stores, from Your stores) so your reports show where it was sold, and webhook_id to follow it with events. Add "wallet_links": true to get Apple Wallet and Google Wallet links in the response.

    POST /api/v1/brand/pins

    Request

    api POST brand/pins '{
        "catalog_id": "3d7e9a1b-2c4f-4e6a-8b0d-9f1e2a3b4c5d",
        "amount": 10000,
        "external_reference": "order-5001",
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "[email protected]",
        "store_id": "5b7c9d1e-3f2a-4b6c-8d0e-1f2a3b4c5d6e",
        "webhook_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
    }'

    Response

    {
        "ok": true,
        "data": {
            "pin_id": "9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90",
            "webhook_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
            "store_id": "5b7c9d1e-3f2a-4b6c-8d0e-1f2a3b4c5d6e",
            "transaction_ref": "3c6bb90c-4dc9-4e01-bdf2-75e91199847d",
            "pin_code": "5260-1815-9083-0164",
            "pin_mask": "5260-****-****-0164",
            "amount": 10000,
            "balance": 10000,
            "currency": "NGN",
            "mode": "test",
            "customer": {
                "first_name": "Jane",
                "last_name": "Doe",
                "email": "[email protected]",
                "phone": null
            },
            "status": "active",
            "external_reference": "order-5001",
            "expires_at": "2027-11-04T10:02:11.000000Z",
            "sold_at": "2026-11-04T10:02:11.000000Z"
        }
    }
    Store pin_id: every later call about this gift card uses it. If your own app shows the code to the customer, save pin_code securely first: we never store codes, so this is the only response that contains it in full. Storing PIN codes.
  4. Look up the gift card

    Fetch its current balance and status (active, blocked or expired). When the customer spends it at your stores, the balance here goes down.

    GET /api/v1/brand/pins/{voucher}

    Request

    api GET brand/pins/9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90

    Response

    {
        "ok": true,
        "data": {
            "pin_id": "9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90",
            "balance": 10000,
            "currency": "NGN",
            "pin_mask": "5260-****-****-0164",
            "past_pin_masks": [
                "5260-****-****-0164"
            ],
            "mode": "test",
            "status": "active",
            "expires_at": "2027-11-04 10:02:11",
            "sold_at": "2026-11-04T10:02:11.000000Z"
        }
    }
  5. Top it up, if the product allows

    On a product with can_top_up: true, add value to a gift card the customer already has. The new balance must stay within the product's min and max, and the amount is taken from your issuing balance.

    POST /api/v1/brand/pins/top-up

    Request

    api POST brand/pins/top-up '{
        "pin_id": "9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90",
        "amount": 5000,
        "external_reference": "order-5002"
    }'

    Response

    {
        "ok": true,
        "data": {
            "pin_id": "9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90",
            "transaction_ref": "a41d7c2e-9b3f-4e8a-b6d1-0c5f2e7a9b31",
            "new_pin_code": null,
            "new_pin_mask": null,
            "amount": 5000,
            "balance": 15000,
            "currency": "NGN",
            "mode": "test",
            "status": "active",
            "external_reference": "order-5002"
        }
    }
    If the product gives a new code after each top-up, new_pin_code holds it, returned only this once; the customer is emailed it too.
  6. Follow its history

    Every movement of value on the gift card, newest first: the sale and top-ups in, and spends at your stores out. Filter by day and page with limit and page.

    GET /api/v1/brand/pins/{voucher}/movements

    Request

    api GET "brand/pins/9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90/movements?limit=20"

    Response (one movement)

    {
        "ok": true,
        "data": [
            {
                "id": "3c6bb90c-4dc9-4e01-bdf2-75e91199847d",
                "external_reference": "order-5001",
                "amount": 10000,
                "currency": "NGN",
                "balance_before": 0,
                "balance_after": 10000,
                "direction": "in",
                "type": "pin_sale",
                "status": "success",
                "mode": "test",
                "created_at": "2026-11-04T10:02:11.000000Z"
            }
        ]
    }
  7. Block, unblock or give a new code

    Block a gift card to stop it being spent, for example when a customer reports it lost, and unblock it when that is resolved. If a code may have been seen by someone else, give the gift card a new code: the old one stops working at once, the balance is unchanged, and the customer is emailed the new code.

    POST /api/v1/brand/pins/block POST /api/v1/brand/pins/unblock POST /api/v1/brand/pins/new-code

    Request

    api POST brand/pins/block '{ "pin_id": "9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90" }'
    
    api POST brand/pins/unblock '{ "pin_id": "9f1c2e7a-4b22-4c10-9a3e-7d2f1b6c8a90" }'

    Response

    {
        "ok": true,
        "data": null
    }
  8. Activate a printed card

    If you sell printed gift cards, look a card up by the number on it to see the amounts it can be loaded with, then activate it for the amount the customer pays. That sells a gift card exactly as above: the face value comes from your issuing balance, the customer is emailed their code, and the card number stops working. Printed cards are live stock, so these calls need your live key.

    POST /api/v1/brand/cards/lookup POST /api/v1/brand/cards/activate

    Request

    api POST brand/cards/lookup '{ "card_number": "9412773018562049615" }'
    
    api POST brand/cards/activate '{
        "card_number": "9412773018562049615",
        "amount": 15000,
        "external_reference": "card-7001",
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "[email protected]"
    }'

    Response to the activation (key fields)

    {
        "ok": true,
        "data": {
            "pin_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
            "pin_mask": "7720-****-****-3318",
            "amount": 15000,
            "currency": "NGN",
            "mode": "live",
            "card_mask": "9412 •••• 615",
            "order_reference": "PVO-7K2M9QXA"
        }
    }

Follow gift cards with webhooks

Pass a webhook_id when selling, or set one later with Change a PIN's webhook, to receive pin.sold, pin.topped_up, pin.spent and pin.expired for that gift card. Webhooks

Sell in bulk with quotes

For a business buying many cards at once, such as staff gifts, send a quote instead of selling card by card. Send a quote with the product, the value and number of cards, and a discount up to the product's max_quote_discount. You get a pay_url to send the buyer; they pay on our hosted page, and the cards are then sent from your issuing balance to the people you listed in recipients, or to whoever the buyer chooses. Pass a webhook_id to hear quote.paid, quote.sent, quote.review, quote.expired and quote.cancelled. Quotes made here also show on your Campaigns page, and those made there in Your quotes.

What your customers can do themselves

Every gift card email and wallet pass links to the customer's card account, whoever sold the card. There, after signing in with a code sent to their email, they can check the balance, freeze the card, get a new code (one a day), keep the same code or set a daily limit, top it up by card (the payment goes to your wallet as a normal top-up), transfer it to someone else, and make family cards funded from it. Your webhook hears each change: pin.topped_up, pin.code_changed (source cardholder), pin.frozen, pin.unfrozen and pin.moved. Webhooks

Handle failures

Failed requests return "ok": false and an error object with a stable code; branch on the code, not the message. These are the ones specific to selling:

Code HTTP When
wrong_account_type 403 A brand key on a /reseller/ path, or the key belongs to an account that can't sell.
issuing_disabled 403 Selling is switched off for your account. Contact your account officer.
catalog_item_not_found 404 No product you sell has that catalog_id.
amount_not_offered 422 A set_amounts product and the amount is not one of its denominations.
amount_too_small 422 The amount, or the balance after a top-up, is below the minimum.
amount_too_large 422 The amount, or the balance after a top-up, is above the maximum.
wallet_balance_low 402 Your issuing balance holds less than the amount.
duplicate_external_reference 422 You already used this external_reference.
invalid_store_id 422 No active store of yours with that ID in this mode.
pin_not_found 404 No gift card with that pin_id sold by you in this mode.
topup_not_allowed 403 The product does not allow top-ups.
pin_blocked 403 The gift card is blocked.
pin_expired 403 The gift card has expired.

All error codes →

Go live

  1. Complete KYC and sign your brand agreement, and ask for live API access to be enabled.
  2. Create a live API key and swap it into your integration. The requests themselves do not change.
  3. Fund your live issuing balance in each currency you will sell, and create your live webhooks.

Next steps