FirstFlip for developers

    Build with the FirstFlip API

    Build crypto payments into your own product. Create charges and payment links, then run the whole flow in the sandbox, where payments are simulated and no real money moves.

    Sandbox: open to approved businessesLive mode: coming soon

    Status

    API status

    Each badge shows the real state of a product today. Build on what runs in the sandbox now, and apply for early access to what comes next.

    Use it today

    Available in sandbox

    Works today with a test key. Payments are simulated and no real money moves.

    • ChargesPrice an order in NGN or USD and get a quote in USDT, USDC or BTC with a hosted payment page.
    • Payment linksOne shareable link. Each customer who continues gets a charge of their own.
    • Sandbox paymentsMark a test charge paid from its hosted page or with one API call.
    • Account checkConfirms which business a key belongs to and that it is a test key.

    Being built

    Coming soon

    Being built for the API. Not open to businesses yet.

    • WebhooksSigned events to your server when a charge is paid, cancelled or expires.
    • Developer assistantIntegration questions answered from the published guides only.
    • Live modeReal payments with live keys. Same endpoints and objects; only the key will change.

    In the app, API planned

    Early access

    Runs in the FirstFlip app today. Its API is planned; apply to be first in line.

    • Virtual USD cardsIssue, fund, freeze and close dollar cards for your customers.
    • Gift cards: sell to FirstFlipLet your customers sell gift cards to FirstFlip from inside your product.
    • Gift cards: buyBuy gift cards for your customers.
    • Crypto deskLet your customers sell crypto and receive Naira.
    • Bill paymentsAirtime, data, electricity and TV, paid in crypto.

    Access

    How to get access

    The API is open to approved businesses. During the beta, the FirstFlip team issues every key by hand.
    1. Apply

      Send the short form on the Business API page. Tell us what you run and what you want to build.

    2. Review

      The FirstFlip team reviews your application and business details, then replies by email.

    3. Receive a sandbox key

      An approved business gets a test key, ffk_test_ followed by 48 hex characters, sent over a private channel. It is shown once, so store it straight away.

    4. Build and test

      Integrate against the sandbox with the guides, the Postman collection and the Node.js SDK that come with your key.

    5. Go live later

      Live keys are not issued yet. When live mode opens, your integration stays the same and only the key changes.

    6. Outside Nigeria?

      Businesses outside Nigeria apply through the same form; the FirstFlip team reviews each case and replies by email.

    Quickstart

    From a test key to a paid test charge

    Four requests take you from a new key to a paid charge, and a fifth creates a payment link to share. Every example runs in the sandbox; swap ffk_test_... for your own key.

    Step 1: Store your key

    Keep the key in an environment variable on your server. Every example below reads it from there.

    bash
    export FIRSTFLIP_SECRET_KEY=ffk_test_...

    Step 2: Check the key

    GET /v1/account confirms the key works and names the business it belongs to. A test key answers with "livemode": false.

    bash
    curl https://api.firstflip.ng/v1/account \
      -H "Authorization: Bearer $FIRSTFLIP_SECRET_KEY"
    Response
    {
      "object": "account",
      "business_name": "Acme Stores",
      "livemode": false,
      "key": "ffk_test_…9f3a"
    }

    Step 3: Create a charge

    Price the order in NGN or USD. FirstFlip quotes it in the asset your customer pays with and returns a hosted payment page. The 201 response is the charge.

    bash
    curl https://api.firstflip.ng/v1/charges \
      -H "Authorization: Bearer $FIRSTFLIP_SECRET_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "amount": 32000,
        "currency": "NGN",
        "pay_asset": "USDT",
        "reference": "order_1042",
        "customer": { "email": "ada@example.com" }
      }'
    Response (shortened)
    {
      "id": "ch_test_4f9a1c2e7b3d5a6f8e9c0b1a",
      "object": "charge",
      "livemode": false,
      "status": "pending",
      "amount": 32000,
      "currency": "NGN",
      "reference": "order_1042",
      "payment": {
        "asset": "USDT",
        "network": "trc20",
        "address": "TEST-TRC20-9c2f4e1a7b3d5f6e8a0b"
      },
      "hosted_url": "https://api.firstflip.ng/v1/sandbox/pay/a1b2c3d4e5f60718293a4b5c6d7e8f90",
      "expires_at": "2026-10-28T13:00:00.000Z"
    }
    • hosted_url: our payment page. Send your customer there, or show the payment details in your own checkout.
    • payment.asset_amount and payment.network: exactly what to send and on which network, quoted at the live rate and locked for 15 minutes (payment.rate_locked_until).
    • payment.address: in the sandbox it starts with TEST- and nothing watches it. Never send real funds to it.
    • expires_at: an unpaid charge expires after 60 minutes unless you set expires_in_minutes.

    Step 4: Read the charge

    Fetch it any time. The status stays pending until the charge is paid, cancelled or expired, and those three are final.

    bash
    curl https://api.firstflip.ng/v1/charges/ch_test_4f9a1c2e7b3d5a6f8e9c0b1a \
      -H "Authorization: Bearer $FIRSTFLIP_SECRET_KEY"

    Step 5: Simulate the payment

    Open hosted_url and press Simulate payment, or call the API. Only a pending charge can be paid; any other status answers 409 charge_not_pending.

    bash
    curl -X POST https://api.firstflip.ng/v1/charges/ch_test_4f9a1c2e7b3d5a6f8e9c0b1a/simulate_payment \
      -H "Authorization: Bearer $FIRSTFLIP_SECRET_KEY" \
      -H "Idempotency-Key: $(uuidgen)"

    The charge now reads paid, with paid_at set. In live mode your customers will pay for real.

    Step 6: Create a payment link

    One link for a chat, a bio or a printed QR. Each customer who presses Continue gets a charge of their own, and opening the link alone creates nothing.

    bash
    curl https://api.firstflip.ng/v1/payment_links \
      -H "Authorization: Bearer $FIRSTFLIP_SECRET_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "amount": 8000,
        "currency": "NGN",
        "description": "Jollof tray",
        "max_uses": 100
      }'
    Response
    {
      "id": "pl_test_7d2c1b0a9e8f6d5c4b3a2918",
      "object": "payment_link",
      "livemode": false,
      "active": true,
      "url": "https://api.firstflip.ng/v1/sandbox/l/5f2d8c1e9a7b4d3c6e0f1a2b3c4d5e6f",
      "amount": 8000,
      "currency": "NGN",
      "description": "Jollof tray",
      "pay_asset": "USDT",
      "pay_network": "trc20",
      "metadata": {},
      "max_uses": 100,
      "uses": 0,
      "created_at": "2026-10-28T12:10:00.000Z"
    }
    • max_uses (1 to 1,000,000) stops the link after that many charges. Leave it out for an unlimited link.
    • POST /v1/payment_links/:id/deactivate stops a link at once. Charges already created are unaffected.
    • reference and customer belong on charges, not links. A charge from a link expires after 60 minutes if unpaid.

    Charge fields

    FieldRequiredNotes
    amount
    Required
    Your price in major units, up to 2 decimals: 32000 is 32,000 Naira and 20.5 is 20.50 dollars.
    currency
    Required
    NGN or USD.
    pay_asset
    Optional
    USDT (default), USDC or BTC.
    pay_network
    Optional
    USDT: trc20 (default) or bep20. USDC: bep20. BTC: bitcoin.
    reference
    Optional
    Your order id, unique per business. A repeat answers 409 duplicate_reference.
    description
    Optional
    Shown to your customer on the hosted payment page.
    customer.name, customer.email
    Optional
    For your records. The name also shows on the hosted page.
    expires_in_minutes
    Optional
    From 15 to 10080 (7 days). Default 60.
    metadata
    Optional
    Up to 20 keys of your own, returned on the charge.

    A minimum charge applies: below it the API answers 400 amount_too_small, and the message states the current minimum. GET /v1/charges lists your charges newest first, 20 per page by default and up to 100 with limit. Filter by status or reference, page with starting_after, and stop when has_more is false.

    Security

    Authentication and key safety

    Every request carries your secret key as a bearer token.
    HTTP header
    Authorization: Bearer ffk_test_...

    What a key looks like

    Test keys are ffk_test_ followed by 48 hex characters. Live keys (ffk_live_) are not issued yet. The key decides the mode: a test key only sees test data, and test ids start ch_test_ or pl_test_.

    Shown once, stored as a hash

    FirstFlip keeps only a one-way hash of your key, so nobody can show it to you again. If you lose it, ask us for a new one.

    Server side only

    Keep the key in an environment variable or a secrets manager. Never put it in a mobile app, browser code, a public repository or a support message.

    Rotate and revoke

    A business can hold up to five active test keys. Ask for a new key, deploy it, then ask us to revoke the old one. Revocation applies from the very next request.

    Think a key has leaked? Ask us to revoke it first, then rotate. Per-key IP allow-lists and self-serve key management in the FirstFlip app are coming soon.

    Reliability

    Errors and idempotency

    Every error has the same shape. Branch on code, which is stable; message is for people and can change, and param names the field at fault when there is one.
    Error response
    {
      "error": {
        "type": "invalid_request_error",
        "code": "invalid_parameter",
        "message": "currency must be NGN or USD",
        "param": "currency"
      }
    }
    HTTPCodeWhat to do
    400
    invalid_parameter
    Fix the field named in param.
    400
    amount_too_small
    Raise the amount. The message states the current minimum.
    400
    invalid_json
    Send valid JSON with Content-Type: application/json.
    400
    idempotency_key_required
    Add an Idempotency-Key header to the POST.
    401
    invalid_api_key
    The key is missing, malformed, unknown or revoked.
    403
    business_inactive
    The business that owns the key is not active. Contact us.
    403
    live_mode_unavailable
    A live key was used. Live mode is not open yet.
    404
    resource_missingnot_found
    Wrong id or path. Another business's ids always look missing.
    409
    duplicate_reference
    You already have a charge with this reference. Fetch it with ?reference=.
    409
    charge_not_pending
    The charge already finished. Fetch it for its final status.
    409
    idempotency_in_progress
    The first request is still running. Retry shortly with the same key.
    413
    body_too_large
    Keep request bodies under 64 KB.
    422
    idempotency_key_reused
    That key was used for a different request. Use a new key.
    429
    rate_limited
    Wait Retry-After seconds, then retry.
    503
    rate_unavailableservice_unavailable
    Exchange rates or FirstFlip are briefly unavailable. Retry with the same key.
    500
    internal_error
    Retry with the same key, and tell us if it persists.

    Idempotency-Key on every POST

    A retry is safe when it carries the same key: FirstFlip runs the operation once and hands every retry the same result. Use a fresh key per operation (a UUID works), up to 255 characters.

    HTTP header
    Idempotency-Key: 7f1c0b9e-5d2a-4c6e-9a51-2b8f0e3d4c71
    • Same key, same request: the original response again, with Idempotent-Replayed: true.
    • Same key, different request: 422 idempotency_key_reused, and nothing happens.
    • Same key while the first request runs: 409 idempotency_in_progress.
    • Results are kept for 24 hours. A 5xx result is not kept, so a retry after a server error runs again.
    • Key order in the JSON body does not matter, and keys are private to your business and mode.

    Rate limits and retries

    Each key can make 300 reads and 60 writes a minute. Over the limit you get 429 rate_limited with a Retry-After header, and responses carry X-RateLimit-Limit and X-RateLimit-Remaining.

    Retry network errors, 409 idempotency_in_progress, 429, 500 and 503 with the same Idempotency-Key and growing waits. Other 4xx errors fail the same way until you change the request.

    Events

    Webhooks

    Coming soon
    Webhooks are not open to beta businesses yet. Until they are, confirm a payment by reading the charge with GET /v1/charges/:id and fulfil an order only when its status is paid.

    How they will work

    • FirstFlip will send charge.paid, charge.cancelled and charge.expired to your endpoint as a signed POST.
    • Each request will carry FirstFlip-Event-Id, FirstFlip-Event-Type and FirstFlip-Signature: t=<unix seconds>,v1=<hex>.
    • To verify one, compute HMAC-SHA256 of t + "." + raw body with your endpoint secret (whsec_...), compare it with v1 in constant time, and reject a t more than 5 minutes from your clock.
    • Answer with any 2xx within 10 seconds. Failed attempts will be retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours.
    • Delivery will be at least once, so store each event id and ignore repeats.
    • Endpoints will need https on port 443 at a public host, with no credentials in the URL.

    Get your handler ready

    Node.js
    const crypto = require("node:crypto");
    
    // rawBody: the exact request body as a string. Do not parse and re-serialise it first.
    function verifyFirstFlip(secret, rawBody, header, toleranceSec = 300) {
      const parts = Object.fromEntries(String(header || "").split(",").map((p) => p.split("=")));
      const t = Number(parts.t);
      if (!Number.isInteger(t) || !parts.v1) return false;
      if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false;
      const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
      const a = Buffer.from(expected, "hex");
      const b = Buffer.from(parts.v1, "hex");
      return a.length === b.length && crypto.timingSafeEqual(a, b);
    }

    Modes

    Sandbox and live

    Live mode: coming soon
    Build everything against the sandbox today. When live mode opens, the same integration takes real payments.
    TopicSandbox todayLive mode (coming soon)
    Keys
    ffk_test_ keys, issued by the FirstFlip team to approved businesses.
    ffk_live_ keys. Not issued yet.
    Money
    None moves. Payments are simulated.
    Real crypto payments from your customers.
    Pay address
    A placeholder that starts with TEST-. Never send real funds to it.
    A real address that receives real crypto.
    Paying a charge
    Simulate payment on the hosted page, or call simulate_payment.
    Your customer will pay. There will be no simulate_payment.
    Quotes
    Real quotes at live rates, locked for 15 minutes.
    The same.
    Settlement
    Nothing is settled.
    Planned: paid charges settled to your business bank account.
    Webhooks
    Coming soon.
    Coming soon.
    Endpoints and objects
    Everything on this page, under /v1.
    Unchanged. Only the key will change.

    Products

    What you can build, and what comes next

    Payments are the first product on the API. Cards, gift cards, the crypto desk and bill payments already run in the FirstFlip app, and their APIs are planned.

    Payments API

    Available in sandbox

    Charges price an order in NGN or USD and quote it in USDT, USDC or BTC, with a hosted payment page or your own checkout. Payment links let you sell from a chat, a bio or a printed QR without a website.

    Start with the quickstart

    Virtual USD cards

    Early access

    In the FirstFlip app today

    The FirstFlip app issues a USD virtual card in three tiers: Blue, Black and Gold. It is paid for and loaded from your FirstFlip crypto balance in USDT, spends in dollars online wherever Visa or Mastercard is accepted, and can be frozen in the app.

    Planned for the API

    • Issue USD virtual cards to your customers
    • Fund each card
    • Freeze a card
    • Close a card

    Gift cards: sell and buy

    Early access

    In the FirstFlip app today

    Sell to FirstFlip: pick the brand, the card country and type (physical card, e-code or receipt) and the amount, see the quote, add the code and photos, then choose payout to a bank account or a crypto wallet. FirstFlip checks every card before paying. Buy: pick a brand and a dollar amount, pay with crypto from your balance, and the card details arrive at the email you give.

    Planned for the API

    • Let your customers sell gift cards to FirstFlip from your product
    • Buy gift cards for your customers

    Crypto desk

    Early access

    In the FirstFlip app today

    Sell crypto in the FirstFlip app and receive Naira in your saved Nigerian bank account.

    Planned for the API

    • Let your customers sell crypto and receive Naira through your product

    Bill payments

    Early access

    In the FirstFlip app today

    Buy airtime and data, and pay electricity and cable TV bills, with crypto from your FirstFlip balance.

    Planned for the API

    • Airtime, data, electricity and TV, paid in crypto from your product

    No API exists yet for cards, gift cards, the crypto desk or bill payments. Apply for early access and tell us which one you need.

    Resources

    Reference, Postman and SDK

    Approved businesses receive the full developer kit together with their sandbox key. The developer docs are not public yet.

    Full API reference

    Every endpoint, field and error code as an OpenAPI 3.1 file, with a rendered reference.

    Postman collection

    A guided walkthrough of every request with a sandbox environment. Set your key and run.

    Node.js SDK (beta)

    A typed client that adds and reuses Idempotency-Keys, retries safely and pages through lists for you.

    Guides

    Quickstart, authentication, charges, payment links, idempotency and errors, testing and going live.

    Support

    Talk to the team

    Questions about access, your key or an integration go to the same team that reviews applications.

    Never paste your secret key into an email or a chat, including one with us.

    Ready to build?

    Apply once. The FirstFlip team reviews your business and sends your sandbox key privately.

    Apply for API access