Skip to main content

Overview

The Payroll API turns a list of staff and salaries into a single approved disbursement. It exists so you stop looping over POST /money-transfer/send once per employee, which gives you no batch-level total, no way to check funding before you start, and no way to stop halfway. The flow is always the same: build a batch, let Hyparrow validate every beneficiary against their bank, confirm the wallet covers it, then approve once.
Base URL: https://api.hyparrow.cloud/api/v1Auth: every request requires X-API-Key and X-API-Secret headers.Sandbox: point at https://sandbox.hyparrow.cloud/api/v1 and use your pk_test_ key. Sandbox batches run the full lifecycle — reserve, send, settle, webhook — without touching a live bank, so you can exercise an entire payroll end to end.
Amounts are in kobo. Every amount on this API is an integer in minor units: 25000000 means ₦250,000.00. Uploaded files are the one exception — a spreadsheet column is read as Naira, because that is what a person typed into it.

How the money moves

This is the part worth reading before you integrate, because it differs from a single transfer.
1

Approval reserves the whole batch at once

When you approve, Hyparrow debits principal + fees for the entire batch from your wallet in one atomic operation and holds it as a reservation. Your balance drops immediately; the held amount appears as reservedBalance.
2

Each payment draws from the reservation

Beneficiary transfers are submitted one at a time, each drawing its own principal and fee out of the reservation. Each gets its own transfer code while staying linked to the batch reference.
3

Anything that never leaves is released

A beneficiary the bank rejects is reversed back into the reservation. When the batch closes, everything unspent is released to your spendable balance.
Reserving up front is what lets Hyparrow promise that an approved payroll can finish. The alternative — debiting per employee as each goes out — allows an unrelated spend to land between employee 40 and employee 41 and strand a payroll half-paid, with no point at which the shortfall could have been refused.
Approval always rests on the account owner’s two-factor authentication. POST /payroll/batches/{id}/approve takes either a current TOTP code (or an unused recovery code) from the account owner, or, for integrations, an API approval mandate the owner armed from the dashboard with their code. An API key on its own can never disburse a payroll.

Quickstart

Create a batch with beneficiaries inline. Hyparrow validates every row and resolves every account name before returning.
Response
reference is the batch-level reference every payment links back to. Send your own clientReference too: it is stored verbatim and echoed on every payroll webhook, so a create call whose response you never received still leaves you holding a key Hyparrow recognises.

Validation

Every row is checked before a batch can be approved. Nothing is ever silently corrected — a row that cannot be paid is returned marked invalid with the reasons in validationErrors, and the merchant fixes it.

Account name matching

Hyparrow resolves every account number with the bank and compares the returned name against the name on the row. The result is in nameMatch:
Approving a batch containing partial or unverified rows requires acknowledgeNameMismatches: true. Without it the request is rejected and lists the beneficiaries in question. A salary paid into the wrong account is not recoverable through this API, so the acknowledgement is deliberate rather than a formality.

Uploading a file

POST /payroll/batches/upload accepts a CSV or XLSX file as multipart form data under the file field. GET /payroll/template returns a ready-made CSV template.
Column headers are matched loosely — Account Number, account_number, Acct No and NUBAN all resolve to the same field — and the header row is located even when the export opens with a company name or a title. Currency symbols, thousands separators and the leading apostrophe Excel adds to preserve a leading zero are all handled. A row whose amount is unreadable is still returned, marked invalid with the reason, so you see every problem in one pass rather than one per re-upload. Limits: 5 MB, .csv / .xlsx.

Checking funding

Call GET /payroll/batches/{id}/funding before you prompt anyone for a code.
If you approve an underfunded batch, the API answers 402 Payment Required with the same three figures in data, so you can tell the merchant exactly how much to add without parsing an error string.

Approving

Approval re-validates the batch before reserving, so the numbers being held are the numbers being approved even if days have passed since the upload. Invalid rows are marked skipped and never paid. The response returns immediately with the batch in approved; submission continues in the background. Send exactly one of code or mandateId. Sending both, or neither, is a 400.

API approval mandates

If your product assembles payroll and your customer (the employer) approves it in your product, your server cannot type the Hyparrow owner’s TOTP code, and it should not hold their authenticator secret. An approval mandate covers this. The owner arms it once from the dashboard with their code, and it then lets one API key approve the batches that key creates, within limits the owner sets.
1

The owner arms a mandate in the dashboard

Under Payroll → Approval mandates, the owner picks the API key, sets the limits, and confirms with their authenticator code. The mandate’s id is what your integration sends. Arming is dashboard-only: no API key can arm a mandate, including its own.
2

Your integration creates and validates the batch with that key

Use the normal batch endpoints. A mandate only ever approves batches created with the same API key it is bound to.
3

Approve with the mandate and name your approver

Send mandateId plus externalApproval, which records who approved the payroll in your product. It is stored on the batch and echoed on payroll.batch.approved, so every run is attributable to a person at the employer.

What a mandate bounds

Your account limits still apply on top. Name-mismatch rows still need acknowledgeNameMismatches: true, exactly as with a code. A mandate is spent atomically with the approval: two approvals racing on one mandate cannot both use its last batch or its remaining monthly allowance, and a failed approval (for example a 402 for insufficient funds) does not use it up. A batch the mandate does not cover is refused with 403 and code: "MANDATE_NOT_USABLE", and the message says which limit it hit. Nothing is reserved or sent. Fall back to an approval with a code, or ask the owner to arm a new mandate. The owner gets an in-app notification when a mandate is armed and every time one approves a payroll.

Managing mandates

A mandate turns an API key into a payroll approver, within its limits. Anyone holding that key can approve payrolls the key creates, up to the mandate’s ceilings, until it expires or is revoked. Use a dedicated key for payroll, keep its secret server-side, restrict it by IP if you can, set the ceilings to what one real payroll needs, and revoke the mandate the moment the key may be exposed. Revoking takes effect immediately and needs no code.

Batch statuses

Item statuses

sent is not success. Like a single transfer, the local record is written when your wallet is debited, before the network confirms anything. Wait for delivered, or for the payroll.item.settled webhook.

Webhooks

Configure your URL with POST /webhooks/settings (see Webhooks). Payroll emits four events, all using the standard event / timestamp / data envelope.
Every item event carries both transferCode and batchReference, so you can reconcile per employee or per run without maintaining your own mapping.

The staff directory

Instead of sending beneficiaries with every batch, keep them in a directory and generate a batch from it. Accounts are verified once and reused, so a monthly run does not re-resolve the same account names every month. Then create a batch with {"name": "...", "payPeriod": "2026-08", "fromDirectory": true} to bill every active employee at their defaultAmount.

Limits

GET /payroll/limits returns the caps in force for your account.
Three independent caps, because they fail differently: beneficiaries per batch, amount per batch, and amount approved per calendar day. The daily cap counts what has been approved, not what has settled — settlement lands minutes later, and counting settled money would let the day’s ceiling be approved several times over inside the settlement window. Contact support to have any of them raised.

Full endpoint reference

Errors

Scheduled payroll — a recurring run that disburses under a bounded standing authorization — is available in the Hyparrow dashboard. It is not exposed on the API, because arming it requires a live second factor from the account owner.