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, always collect this figure, never totalAmount. The service fee and VAT are settled independently. serviceFee and vatAmount are what the customer is charged for each: a component the merchant absorbs reports 0 and is deducted from their payout instead. serviceFeeDirection and vatDirection tell you which side each one landed on, and taxDirection summarizes them as merchant, customer, or split when they differ. So a merchant who absorbs the 2.5% but passes VAT to the customer produces serviceFee: 0, a non-zero vatAmount, and taxDirection: "split". Render whichever fee rows are non-zero rather than keying off taxDirection, see Fees & tax direction.

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