Skip to main content
Every invoice you create can be paid on a hosted checkout page. You share a URL, your customer opens it, picks how they want to pay, and Hyparrow settles the invoice and notifies you. The checkout URL is:
The endpoints on this page are the public API the hosted checkout page calls. They are unauthenticated — there are no X-API-Key / X-API-Secret headers because the customer paying the invoice is not your API client. The base URL is the same: https://api.hyparrow.cloud/api/v1.
Amounts are in kobo. payableAmount, totalAmount and friends are in the minor unit (100000 = ₦1,000.00).

Payment methods

The customer-facing options on the checkout page are: Which methods appear depends on the merchant’s payment preferences and the invoice currency — the NGN-only rails (bank transfer, USSD) are hidden for non-NGN invoices. The active set is returned as allowedPaymentMethods from GET /checkout/{invoiceId}.

End-to-end flow

1

Customer opens the checkout page

The page calls GET /checkout/{invoiceId} to load the invoice, amounts and allowed payment methods.
2

Customer picks a method

Depending on the choice, the page calls …/va, …/ussd, …/card, …/opay or …/crypto to start that payment.
3

Customer pays

Transfer to the virtual account, dial the USSD code, complete the card / OPay redirect, or send crypto to the address.
4

Page polls for status

The page polls GET /checkout/{invoiceId}/status until paid is true.
5

Callback fires

Hyparrow POSTs a checkout.payment.* event to the invoice’s checkoutCallbackUrl (if set) as the payment progresses.

Load the checkout

GET /checkout/{invoiceId} Returns sanitized invoice data for rendering the page. Canceled invoices return 404.
payableAmount is what the customer actually pays. When the invoice’s tax_direction is customer, the service fee (capped) and VAT are added on top of totalAmount; when it’s merchant, payableAmount equals totalAmount and the fees are settled from the merchant’s payout.

Bank transfer (virtual account)

POST /checkout/{invoiceId}/va Creates (or returns the existing active) virtual account for the invoice.
expiryMinutes accepts 10, 15 or 30; anything else defaults to 15.
If a non-expired VA already exists it is returned with 200 instead of creating a duplicate. GET /checkout/{invoiceId}/va returns the current active, non-expired account, or 404 if there is none (e.g. it expired).
The customer must transfer the exact amount before expiresAt. After expiry the account stops accepting payment — call POST …/va again to mint a fresh one. Bank transfer is NGN only; a non-NGN invoice returns 400.

USSD

POST /checkout/{invoiceId}/ussd Generates a USSD dial code for the customer’s bank.
The customer dials the code to authorize the transfer. USSD is NGN only. A checkout.payment.pending callback fires when the code is generated.

Card

POST /checkout/{invoiceId}/card Charges a card via the card network. Card details are encrypted before they leave the browser.
The response carries the network’s responseCode:
  • 00 — approved. The invoice is settled and a checkout.payment.completed callback fires.
  • T0 / S0 — processing (e.g. an OTP/3-DS step). A checkout.payment.pending callback fires; keep polling status.
  • anything else — declined. A checkout.payment.failed callback fires.

OPay

POST /checkout/{invoiceId}/opay Initializes an OPay payment and returns a redirectUrl to send the customer to.
After authorizing on OPay the customer is returned to the checkout page, which polls status to confirm. A checkout.payment.pending callback fires on initialization.

Crypto

POST /checkout/{invoiceId}/crypto Generates a stablecoin payment address. The fiat total is converted to USD.
token is USDC or USDT. The customer sends the asset to address before expiresAt (30 minutes). GET /checkout/{invoiceId}/crypto returns the active address. Settlement arrives asynchronously once the on-chain transfer confirms.

Poll status

GET /checkout/{invoiceId}/status The checkout page polls this until the payment lands.
paid is true once the invoice is paid (or a receipt exists). Use this as the source of truth on the page; the asynchronous methods (transfer, USSD, OPay, crypto) flip it when settlement confirms. A payment link lets you collect a payment without pre-creating an invoice — useful for a shareable “pay me” page. The public URL is CHECKOUT_BASE_URL/pay/{identifier}, where identifier is the link’s custom slug or its id. GET /checkout/pay/{identifier} returns the link’s page data (name, description, amount, allowed methods):
POST /checkout/pay/{identifier} turns the customer’s submission into an invoice so the normal payment rails can settle it. If the link has a fixed amount it wins; otherwise the customer-entered amount is used.
Hand the returned invoiceId to the standard checkout flow above (…/va, …/card, etc.) to complete payment.

Edge cases

Checkout callback webhook

If the invoice has a checkoutCallbackUrl, Hyparrow POSTs an event to it as the payment progresses. The body is:
Events:
  • checkout.payment.pending — payment started, awaiting settlement (transfer pending, USSD/OPay in progress, card in 3-DS).
  • checkout.payment.completed — settled; the invoice is paid.
  • checkout.payment.failed — the attempt failed (e.g. a declined card).
Treat the callback as a notification and confirm with GET /checkout/{invoiceId}/status before fulfilling.

Sandbox

In the sandbox, the hosted checkout URL carries ?sandbox=true and points at the sandbox API (https://sandbox.hyparrow.cloud/api/v1). You don’t need a real bank transfer or card to test — complete the payment instantly:
Use the “Simulate payment” button on the sandbox checkout page, or call POST /checkout/{invoiceId}/simulate-payment directly. It runs the invoice through the normal payment path — marking it paid, issuing a receipt and firing the checkout.payment.completed callback — with simulated money. It is available only on the sandbox deployment (returns 404 in production).
If the invoice is already paid, it returns 200 with "invoice already paid".