Developer documentation

API Reference

The Insureflow REST API lets insurance companies and brokers integrate premium collection, policy management, and settlement reporting directly into their own systems. Every endpoint below reflects the live API.

Overview

The API is organized around REST: predictable resource-oriented URLs, JSON request and response bodies, and standard HTTP verbs and status codes.

Base URL

https://api.insureflowapp.tech/api/v1https://sandbox.api.insureflowapp.tech/api/v1 (sandbox)

Authentication

Every request other than onboarding and login carries Authorization: Bearer <token>, obtained from POST /auth/login. Tokens expire after 60 minutes — there is no refresh endpoint; log in again to get a new one.

Sandbox API keys

Evaluating an integration? Ask Insureflow for sandbox access. You'll receive two API keys for the sandbox base URL: an insurer key(acts as your sandbox insurance company's admin) and a broker key (acts as a test broker already assigned to that insurer, so you can exercise the payment endpoints too). Send the key on every request instead of a Bearer token:

X-API-Key: ifk_sandbox_…

Keys don't expire hourly like Bearer tokens, but sandbox access has an expiry date set by Insureflow, and a key can be rotated or revoked at any time. Keep them server-side; never ship one in a browser or mobile app. Don't send a Bearer token and an API key on the same request. Sandbox keys only work against the sandbox, and no real money moves there.

Roles & permissions

Every account belongs to exactly one insurance company or one broker, and holds one of four roles. An Insureflow Admin operates the platform itself and isn't a counterparty you'll integrate as.

RoleScope
Insurance Company AdminManages one insurance company: creates policyholders and policies, assigns servicing brokers, sets the settlement account, and reads everything under that company.
Broker AdminManages one brokerage: creates staff logins and has full transacting access — collecting premiums, viewing reports, sending reminders.
Broker StaffSame transacting access as Broker Admin, minus the ability to manage staff.

Money & rate conventions

Every monetary amount is an integer number of kobo (₦1 = 100 kobo) — never a float. Every commission rate is an integer number of basis points (1 bps = 0.01%).

Idempotency

POST /remittances requires an Idempotency-Key header — a UUIDv4 you generate. Retrying the identical request with the same key is safe and returns the original remittance instead of creating a second one; reusing the key with a different body is rejected with 409.

Rate limiting

60 requests/minute per broker, 120 requests/minute per IP. Exceeding either returns 429.

Error responses

Errors are a JSON body of the shape { "detail": "..." }.

StatusMeaning
400Bad request — malformed input.
401Missing or invalid Bearer token or API key (including an expired or revoked sandbox key).
403Authenticated, but your role lacks the required permission.
404The resource doesn't exist, or doesn't belong to your organization.
409Conflict — e.g. the Idempotency-Key was already used with a different request body.
422Validation error — see detail for the specific field.
429Rate limit exceeded — back off and retry.
502Upstream payment gateway error — transient, safe to retry with a new Idempotency-Key.

Authentication

Every insurance company and broker account activates once with a one-time token, then authenticates with a short-lived Bearer token for every subsequent call. Sandbox integrations can instead send an X-API-Key header on every call, using the insurer or broker key Insureflow issued for sandbox access.

POST/auth/activate

Activate a new account

No authentication required

Called once, after your organization has been approved and a one-time activation token has been issued to your contact email. Sets the account's password and marks it active. The token is single-use.

Request body

FieldTypeDescription
token*stringThe one-time activation token.
password*stringChosen password.

HTTP 204 No Content on success.

POST/auth/login

Obtain a Bearer token

No authentication required

Standard OAuth2 password flow. Sends credentials as application/x-www-form-urlencoded form data, not JSON.

Request body

FieldTypeDescription
username*stringAccount email address.
password*stringAccount password.

Response

FieldTypeDescription
access_tokenstringJWT Bearer token, valid 60 minutes.
token_typestringAlways "bearer".
  • Login is blocked while your organization (insurance company or broker) is pending, rejected, or suspended.
PATCH/auth/change-password

Change your own password

Any authenticated role

Changes the password of the currently authenticated account.

Request body

FieldTypeDescription
current_password*stringCurrent password, for verification.
new_password*stringNew password.

Response

FieldTypeDescription
access_tokenstringA fresh Bearer token (the old one stays valid until it expires).
token_typestringAlways "bearer".
POST/auth/forgot-password

Email a password-reset link

No authentication required

Emails the account a link to choose a new password. The link works once, for 30 minutes. Also works when a one-time password has expired.

Request body

FieldTypeDescription
email*stringThe account's email address.

HTTP 204 No Content whether or not the address has an account, so it can't be used to find out who does. Limited to 5 requests an hour per address.

POST/auth/reset-password

Set a new password from a reset link

No authentication required

Sets the password using the token from the emailed link, then the token stops working.

Request body

FieldTypeDescription
token*stringThe token from the reset link.
new_password*stringAt least 8 characters.

HTTP 204 No Content on success; 422 if the link is invalid, used or expired.

Insurance Companies

Onboarding is unauthenticated; every other endpoint requires a Bearer token of one of the company's logins (its admin, or its staff where noted), scoped to that one company.

POST/insurance-companies/me/staff

Give a staff member a login

Insurance Company Admin

Creates an Insurance Company Staff login. Staff can create policies and clients, send reminders, set premium references and see every report, but can't manage brokers, staff, the settlement account or webhooks, or mark a premium paid by hand. Their one-time password is emailed to them.

Request body

FieldTypeDescription
email*stringThe staff member's email; must not already have an account.

Response

FieldTypeDescription
otpstringTheir one-time password, also emailed. Shown once.
GET/insurance-companies/me/staff

List your company's logins

Insurance Company Admin

Every login of your company, the admin first.

Response

FieldTypeDescription
id / email / rolestringinsurance_company_admin or insurance_company_staff.
is_activebooleanFalse once deactivated.
invite_pendingbooleanTrue until they've signed in and set their own password.
DELETE/insurance-companies/me/staff/{user_id}

Deactivate a staff member

Insurance Company Admin

Ends a staff member's access immediately. The admin login can't be deactivated.

Path parameters

FieldTypeDescription
user_id*UUIDThe staff login's id.
POST/insurance-companies/me/brokers/onboard

Add a new broker you work with

Insurance Company Admin

Creates a broker that isn't on Insureflow yet, approves it for you at once and adds it to your brokers. Other insurers don't see it until Insureflow approves it for everyone. Its admin is emailed a welcome with a one-time password; once signed in, they add the broker's BVN and phone number (PATCH /virtual-accounts/me), which creates the account it pays premiums into, and its team.

Request body

FieldTypeDescription
name*stringThe broker's business name.
contact_email*stringIts admin's email; must not already have an account.
bvnstringOptional: 11 digits. Given with phone_number, the account number is created at once instead of when the broker adds them.
phone_numberstringOptional, with bvn.

Response

FieldTypeDescription
brokerobjectThe broker, approved, with approved_for_insurer_id set to you.
otpstringIts admin's one-time password, also emailed. Shown once.
POST/insurance-companies/me/brokers/import/preview

Check a bulk broker upload

Insurance Company Admin

Multipart upload (field: file) of an .xlsx or .csv with two columns, Broker Name and Email (GET /insurance-companies/me/brokers/import/template for a blank .xlsx). Reports which rows would be added and why the rest wouldn't: a missing name, an invalid or repeated email, an email that already has an account, or a broker already on Insureflow (add it with POST /insurance-companies/me/brokers instead). Writes nothing. Up to 200 rows.

Response

FieldTypeDescription
total_rowsintegerNon-blank rows in the file.
readyBrokerImportRow[]row_number, name and contact_email of each broker that would be added.
errorsBrokerImportRowError[]row_number, contact_email and reason for each that wouldn't.
file_errorstring | nullWhy the whole file couldn't be read, e.g. a missing column.
POST/insurance-companies/me/brokers/import

Add brokers in bulk

Insurance Company Admin

Adds the rows a preview returned as ready (re-checked first). Each broker is approved for you only and added to your brokers, as with Add a new broker, and its admin is emailed a welcome with a one-time password. Rows that fail are reported without stopping the rest.

Request body

FieldTypeDescription
rows*BrokerImportRow[]The preview's ready rows.

Response

FieldTypeDescription
created_countintegerBrokers added.
createdobject[]broker_id, name, contact_email and otp (its admin's one-time password, also emailed; shown once) of each.
errorsBrokerImportRowError[]Rows not added, and why.
GET/insurance-companies/me/broker-requests

List brokers asking to work with you

Insurance Company Admin

Brokers waiting for approval that named you when they signed up (requested_insurer_id).

Response

FieldTypeDescription
[]BrokerPending brokers, oldest first.
PATCH/insurance-companies/me/broker-requests/{broker_id}/approve

Approve a broker's request

Insurance Company Admin

Approves the broker for you only, creates its account number, and adds it to your brokers.

Path parameters

FieldTypeDescription
broker_id*UUIDThe requesting broker.
PATCH/insurance-companies/me/broker-requests/{broker_id}/decline

Decline a broker's request

Insurance Company Admin

Not a broker you work with. It isn't rejected: it waits for Insureflow to review it instead.

Path parameters

FieldTypeDescription
broker_id*UUIDThe requesting broker.
GET/insurance-companies/me/broker-directory

Search brokers you can work with

Insurance Company Admin

Insureflow-approved brokers, searchable by name or email, each flagged with whether it's already one of yours.

Query parameters

FieldTypeDescription
qstringMatches name or email, case-insensitive.
limitintegerUp to 100 (default 50).

Response (array item fields)

FieldTypeDescription
id / name / contact_emailThe broker.
assignedbooleanAlready working with you.
POST/insurance-companies/me/brokers

Add a broker

Insurance Company Admin

Start working with an approved broker -- no Insureflow step needed. You can then create policies for them, and they can remit your premiums. Adding a broker you already have returns the existing assignment.

Request body

FieldTypeDescription
broker_id*UUIDFrom the broker directory.

Response

FieldTypeDescription
idUUIDThe assignment.
broker_id / insurance_company_idUUID
is_active / assigned_at / ended_at
  • 409 if the broker isn't approved; 404 if it doesn't exist.
DELETE/insurance-companies/me/brokers/{broker_id}

Remove a broker

Insurance Company Admin

Stop working with a broker: no new policies can be created for them. Their existing policies, open remittances and payment history are kept.

Path parameters

FieldTypeDescription
broker_id*UUIDThe broker's UUID.

The ended assignment.

POST/insurance-companies

Submit onboarding application

No authentication required

Registers a new insurance company. It starts in pending status and must be approved by an Insureflow operator before the contact email receives an activation token.

Request body

FieldTypeDescription
name*stringLegal company name.
contact_email*stringPrimary admin contact email.

Response

FieldTypeDescription
companyInsuranceCompanyOutThe created company record (see GET below for its full shape).
otpstringOne-time activation token — only ever returned here. Store or relay it immediately.
GET/insurance-companies/{company_id}

Get company details

Insurance Company Admin (own company only)

Returns the current state of the insurance company record.

Path parameters

FieldTypeDescription
company_id*UUIDThe company's UUID.

Response

FieldTypeDescription
idUUIDCompany UUID.
namestringLegal name.
contact_emailstringAdmin contact email.
statusstringpending | approved | rejected | suspended.
rejection_reasonstring | nullPopulated if rejected.
settlement_bank_codestring | nullnull until a settlement account is set.
settlement_account_numberstring | nullnull until a settlement account is set.
settlement_account_namestring | nullnull until a settlement account is set.
parent_company_idUUID | nullSet only for a subsidiary under a parent organization grouping.
created_atdatetimeISO 8601 UTC.
GET/insurance-companies/{company_id}/brokers

List brokers assigned to this company

Insurance Company Admin (own company only)

Every broker with an active assignment to this insurer — the many-to-many relationship, not a fixed 1:1 broker.

Path parameters

FieldTypeDescription
company_id*UUIDThe company's UUID.

Array of broker objects (same shape as the Brokers section's GET response).

POST/insurance-companies/{company_id}/settlement-account

Set settlement bank account

Insurance Company Admin (own company only)

Registers the account Insureflow transfers premium proceeds into (net of commission) after each successful payment. Performs a live NIBSS name-enquiry lookup before saving — the response's settlement_account_name is whatever that lookup confirmed, never a value you supply directly.

Path parameters

FieldTypeDescription
company_id*UUIDThe company's UUID.

Request body

FieldTypeDescription
bank_code*string6-digit NIP institution code, e.g. GTBank = 000013. Not the 3-digit CBN code — see the Appendix.
account_number*string10-digit NUBAN account number.

Returns the updated company object.

  • Required before brokers can remit to you: Insureflow pays you the moment a broker's transfer arrives, so it won't accept a remittance it couldn't pay out.
  • The NIBSS lookup is real even in sandbox — a dummy account number returns HTTP 424.

Brokers

Broker onboarding is initiated by the broker (unauthenticated). A broker must be approved by Insureflow and assigned to an insurer before it can create staff or transact against that insurer's policies.

POST/brokers

Submit broker onboarding application

No authentication required

Registers a new broker. In production it starts pending until Insureflow approves it; approval creates the broker's Insureflow account number (the account premiums are paid into). On the sandbox it's approved at once and gets its account straight away.

Request body

FieldTypeDescription
name*stringLegal broker organization name.
contact_email*stringPrimary admin contact email.
bvn*string11-digit Bank Verification Number, needed to create the broker's account. Not needed on the sandbox, which uses a test BVN.
phone_number*stringContact phone number, needed to create the broker's account. Not needed on the sandbox.

Response

FieldTypeDescription
brokerBrokerOutThe created broker record.
otpstringOne-time activation token — only ever returned here.
GET/brokers/{broker_id}

Get broker details

Broker (own record) or Insureflow Admin

Broker roles can only retrieve their own broker record.

Path parameters

FieldTypeDescription
broker_id*UUIDThe broker's UUID.

Response

FieldTypeDescription
idUUIDBroker UUID.
namestringBroker name.
contact_emailstringAdmin contact email.
statusstringpending | approved | rejected | suspended.
rejection_reasonstring | nullPopulated if rejected.
squad_va_numberstring | nullThe broker's own collection virtual account number, once provisioned.
squad_va_bankstring | nullBank the virtual account is held at.
created_atdatetimeISO 8601 UTC.
GET/brokers/{broker_id}/insurers

List insurers this broker works with

Broker (own record) or Insureflow Admin

Every insurer with an active assignment to this broker.

Path parameters

FieldTypeDescription
broker_id*UUIDThe broker's UUID.

Array of insurance company objects (same shape as the Insurance Companies section's GET response).

GET/brokers/me/team

List your team

Broker Admin

Every login of your broker, admins first.

Response (array item fields)

FieldTypeDescription
id / email / roleUUID / string / stringrole is broker_admin or broker_staff.
is_activebooleanFalse once deactivated.
invite_pendingbooleanTrue until they've signed in and chosen a password.
is_youbooleanYour own login, whose role and access you can't change.
POST/brokers/me/team

Add someone to your team

Broker Admin

Creates a login under your broker, as staff (see and pay premiums) or another admin (also manages the team, webhooks and your account details). Their one-time password is emailed to them. POST /brokers/{broker_id}/staff does the same.

Request body

FieldTypeDescription
email*stringMust not already have an account.
rolestringbroker_staff (default) or broker_admin.

Response

FieldTypeDescription
otpstringTheir one-time password, also emailed. Shown once.
PATCH/brokers/me/team/{user_id}

Change a team member's role

Broker Admin

Makes them an admin or staff, from their next request. Not your own login, so your broker always keeps an admin.

Path parameters

FieldTypeDescription
user_id*UUIDThe team member.

Request body

FieldTypeDescription
role*stringbroker_admin or broker_staff.

Returns the team member, as in the list.

DELETE/brokers/me/team/{user_id}

Deactivate a team member

Broker Admin

Ends their access at once. Not your own login.

Path parameters

FieldTypeDescription
user_id*UUIDThe team member.

Returns the team member, as in the list.

Policyholders

Policyholders are the insured individuals a policy covers. Creation is an Insurance Company Admin action — a broker is assigned as the servicing broker on each policyholder, but doesn't create the record itself. Creating one first is optional: POST /policies can carry a new client's details and creates them with the policy. The API path is /users for historical reasons; the schema below is what it actually returns.

POST/users

Create a policyholder

Insurance Company Admin

full_name is the only required field; the rest improve reminder delivery and identity verification.

Request body

FieldTypeDescription
broker_id*UUIDThe broker who will service this policyholder — must have an active assignment to your company.
full_name*stringFull legal name.
emailstringFor payment reminders.
phone_numberstringLocal format, e.g. 08012345678.
identification_numberstringGovernment ID (NIN, passport, etc.) — encrypted at rest.

Response

FieldTypeDescription
idUUIDPolicyholder UUID — required to create a policy.
broker_idUUIDThe servicing broker.
insurance_company_idUUIDThe underwriting company.
broker_namestringDenormalized broker name, for display.
insurance_company_namestringDenormalized company name, for display.
full_namestringAs provided.
emailstring | nullnull if not provided.
phone_numberstring | nullnull if not provided.
identification_numberstring | nullnull if not provided. Masked in most UI surfaces, plaintext over the API to the owning tenant.
created_atdatetimeISO 8601 UTC.
GET/users/{policyholder_id}

Get a policyholder

Insurance Company Admin or the assigned Broker

Path parameters

FieldTypeDescription
policyholder_id*UUIDThe policyholder's UUID.

Same shape as the create response above.

GET/users

List policyholders

Insurance Company Admin or Broker

An Insurance Company Admin sees every policyholder across their assigned brokers by default; a broker sees only their own.

Query parameters

FieldTypeDescription
broker_idUUIDNarrow to one broker. Broker roles may only pass their own broker_id.

Array of policyholder objects.

Policies

A policy records a policyholder's cover terms. Creating one auto-generates a rolling window of 12 upcoming installment rows in due status — one per payment period. Creation is an Insurance Company Admin action, naming which broker services the policy.

POST/policies

Create a policy

Insurance Company Admin

Request body

FieldTypeDescription
policyholderobjectA new client's details, saved with the policy: full_name (required), email, phone_number, identification_number. If this broker already has a client with that email, the policy is added to them. Give this or policyholder_id.
policyholder_idUUIDAn existing policyholder of this broker, instead of policyholder.
broker_id*UUIDThe broker servicing this policy — must have an active assignment to your company.
reference_number*stringThe debit note — unique across all policies. A renewal or endorsement gets a new one.
policy_number*stringYour policy number (1–100 characters, trimmed). Unlike the debit note it may repeat: several debit notes can belong to the same policy.
premium_amount_kobo*integerThe policy's one premium for its whole duration, in kobo. It's paid in equal payments by premium_frequency — ₦120,000 over 1 year monthly is 12 payments of ₦10,000 — the first payments carrying any leftover kobo.
premium_frequency*stringHow the premium is paid: monthly | quarterly | annually.
start_date*dateISO 8601 (YYYY-MM-DD). The first premium is due on this date.
policy_typestringOpen string label. Defaults to GENERIC — any value is accepted (LIFE, MOTOR, HEALTH, ...).
policy_namestringOptional display name for the policy.
duration_months*integerThe policy's term in months, a whole number of years: 12, 24 … 120. The first payment is due on start_date.
coverage_amount_kobointegerOptional sum insured, in kobo.
coverage_itemsstringOptional free-text coverage detail.
beneficiariesstringOptional free-text beneficiary list.
broker_notesstringOptional notes visible to the servicing broker.
internal_tagsstringOptional comma-separated tags.

Response

FieldTypeDescription
idUUIDPolicy UUID — required to list installments.
policyholder_id / broker_id / insurance_company_idUUIDThe three parties.
reference_numberstringThe debit note.
policy_numberstring | nullYour policy number. null only for policies created before policy numbers were introduced.
policy_typestringType label.
premium_amount_kobointegerThe premium for the whole duration, in kobo (when premium_is_total).
premium_is_totalbooleantrue: premium_amount_kobo is split into payment_count equal payments. false: a policy created before premiums were split, charged that amount on every payment.
premium_frequencystringHow the premium is paid: monthly | quarterly | annually.
duration_months / payment_countintegerThe term in months, and how many payments it's paid in (12 months monthly: 12).
start_datedatePolicy start date — the first payment's due date.
statusstringactive | cancelled.
policyholder_name / broker_name / insurance_company_namestringDenormalized names, for display.
created_atdatetimeISO 8601 UTC.
GET/policies/{policy_id}

Get a policy

Insurance Company Admin or the servicing Broker

Path parameters

FieldTypeDescription
policy_id*UUIDThe policy's UUID.

Same shape as the create response above.

GET/policies

List policies

Insurance Company Admin or Broker

user_id narrows to one policyholder's policies; omitted returns every policy you can see.

Query parameters

FieldTypeDescription
user_idUUIDNarrow to one policyholder.

Array of policy objects.

GET/policies/{policy_id}/installments

List a policy's installments

Insurance Company Admin or the servicing Broker

The full installment schedule for one policy, in due-date order.

Path parameters

FieldTypeDescription
policy_id*UUIDThe policy's UUID.

Array of installment objects — see the Installments section for the full field list.

GET/policies/import/template

Download the bulk-import template

Insurance Company Admin

An .xlsx with the expected columns and two example rows, plus a second sheet listing your assigned brokers (name and email) for the Broker column.

The .xlsx file itself (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).

POST/policies/import/preview

Preview a bulk policy import

Insurance Company Admin

Upload a filled-in template as multipart form data (field name: file). Every row is validated — broker assignment, debit-note uniqueness, amounts, dates — and reported as ready or with a reason. Nothing is written.

Request body

FieldTypeDescription
file*file (.xlsx)Max 5 MB and 500 non-blank rows. Required columns: Debit Note, Policy Number, Broker (name or email), Client Name, Premium Amount (NGN), Frequency, Start Date.

Response

FieldTypeDescription
total_rowsintegerNon-blank data rows in the file.
readyPolicyImportRow[]Rows that would import, normalized (broker_id, premium_amount_kobo, start_date, existing_client_id when an existing client will be reused).
errors{row_number, debit_note, reason}[]Rows that won't import, and why.
file_errorstring | nullSet when the file itself is unusable (e.g. a required column is missing).
POST/policies/import

Import policies in bulk

Insurance Company Admin

Creates one policy (and its premium schedule) per row, reusing a client when their email matches an existing client of the same broker. Every row is re-validated, so pass the preview's ready rows back unchanged.

Request body

FieldTypeDescription
rows*PolicyImportRow[]The ready rows from the preview, up to 500.

Response

FieldTypeDescription
created_countintegerPolicies created.
policy_idsUUID[]The new policies, in row order.
errors{row_number, debit_note, reason}[]Rows rejected at import time, e.g. a debit note taken since the preview.
  • A row that fails never blocks the rest, and never leaves a client created without its policy.

Installments

Each due date on a policy is its own installment row. Premiums are never auto-debited — a broker must explicitly initiate payment against a due or overdue installment_id.

GET/installments

List installments

Insurance Company Admin or Broker

Scoped to everything you can see: your whole tenant for an insurer, your own broker's book across every insurer you work with for a broker (narrow with insurer_id).

Query parameters

FieldTypeDescription
statusstringdue | overdue | paid | cancelled.
policy_typestringFilter by the policy's type label.
idsstringComma-separated installment UUIDs — fetch an exact set in one call.
qstringCase-insensitive search across the policy's debit note, its policy number, the installment's own reference and the client's name (max 100 characters).
insurer_idUUIDBroker roles only: narrow to one insurer.

Response (array item fields)

FieldTypeDescription
idUUIDInstallment UUID — the value POST /remittances takes.
policy_idUUIDThe parent policy.
due_datedateWhen this installment is due.
amount_kobointegerAmount owed, in kobo.
statusstringdue | overdue | paid | cancelled.
payment_idUUID | nullSet once paid via a single payment.
payment_batch_item_idUUID | nullSet once paid via a bulk batch. Mutually exclusive with payment_id.
reference_numberstring | nullYour own per-installment reference, if set.
policy_reference_numberstring | nullThe parent policy's debit note.
policy_numberstring | nullThe parent policy's policy number.
policy_typestringDenormalized from the parent policy.
policyholder_name / broker_name / insurance_company_namestringDenormalized names, for display.
payment_datedatetime | nullWhen this installment was actually paid — null until then. Distinct from settlement date.
created_atdatetimeISO 8601 UTC.
PATCH/installments/{installment_id}/reference-number

Set an installment's reference number

Insurance Company Admin or Broker

Attaches your own debit-note or reconciliation reference to a specific installment — useful for matching an Excel bulk-pay upload against your own records.

Path parameters

FieldTypeDescription
installment_id*UUIDThe installment's UUID.

Request body

FieldTypeDescription
reference_numberstring | nullPass null to clear it.

Returns the updated installment object.

Remittances

A remittance is how a broker pays premiums: a list of premiums (one or up to 500, all for one insurer) and the exact amount to transfer for them. Creating one moves no money. The broker transfers that exact amount into their permanent Insureflow account — the amount alone identifies the remittance, so the narration doesn't matter. Insureflow marks every premium paid and pays the insurer straight away with one transfer per premium, each narrated with the client's name, policy number and debit note, so every premium is its own credit on the insurer's statement: what the broker sent, less Squad's fees and Insureflow's 0.5%.

POST/remittances

Create a remittance

Broker Admin or Broker Staff

Every premium must be yours, due or overdue, not already in another open remittance, and for the same insurer, and that insurer must have confirmed its settlement account. You can't have two open remittances of the same total (409) — the amount is what identifies your transfer. Requires an Idempotency-Key header. Rate-limited per broker and per IP.

Request body

FieldTypeDescription
installment_ids*UUID[]1–500 installment UUIDs.

Response

FieldTypeDescription
idUUIDRemittance UUID.
referencestringe.g. IFW-7K3QX — optional in the transfer narration; it appears on the insurer's transfers.
statusstringopen | paid | cancelled | expired.
total_net_kobointegerThe exact amount to transfer: each premium's gross less your broker commission.
pay_toobject | nullWhile open: virtual_account_number, virtual_account_bank, amount_kobo, reference, expires_at.
same_total_warningbooleanOnly for remittances opened before totals had to be unique: another open one has the same total, so the narration reference is what tells them apart.
settlement_statusstring | nullOnce paid: in_progress | settled | payout_failed — the state of the per-premium transfers to the insurer.
premiums_settledintegerHow many premiums' transfers have reached the insurer.
linesarrayOne per premium: debit note, policy number, client, gross_kobo, broker_commission_kobo, net_kobo; once paid also squad_fee_kobo, insureflow_fee_kobo, payout_fee_kobo, insurer_net_kobo and settlement (that premium's own transfer: id, status, amount_kobo, squad_transfer_ref, narration, attempt_number, failure_reason).
expires_at / paid_at / cancelled_atdatetimeAn open remittance expires after 14 days and releases its premiums.
  • Send exactly total_net_kobo. A different amount doesn't pay the remittance — it waits as a receipt for you to say what it was for, or to ask for it back.
  • No part payments: a premium is either fully in a remittance or not in it.
GET/remittances/{remittance_id}

Get a remittance

The broker, the insurer, or Insureflow Admin

Poll this until status leaves open.

Path parameters

FieldTypeDescription
remittance_id*UUIDThe remittance's UUID.

Same shape as the create response above.

GET/remittances

List remittances

Broker, Insurance Company Admin or Insureflow Admin

Scoped to what you can see: your own as a broker, those for your company as an insurer.

Query parameters

FieldTypeDescription
statusstringopen | paid | cancelled | expired.
broker_id / insurer_idUUIDNarrow further.
limitintegerUp to 200 (default 50).

Array of remittance objects, newest first.

POST/remittances/{remittance_id}/simulate-transfer

Simulate the broker's transfer (sandbox only)

Broker Admin or Broker Staff

Sandbox only — 404 on production. Has the bank's sandbox simulator send this open remittance's exact total into your Insureflow account, then processes it immediately, exactly as a real transfer would be: the premiums are marked paid and each premium's payout to the insurer is sent from Insureflow's sandbox Squad balance. No real money moves. The simulator carries no narration, so the transfer is matched by amount alone.

Path parameters

FieldTypeDescription
remittance_id*UUIDAn open remittance of yours.

The remittance — normally now paid, with each line's fees and insurer net filled in. If the simulator is slow to report the transfer it's returned still open and is paid within a few minutes; poll GET /remittances/{remittance_id}. 409 if the remittance isn't open.

  • Rate-limited like POST /remittances.
POST/remittances/{remittance_id}/cancel

Cancel a remittance

Broker Admin or Broker Staff

Only an open remittance; its premiums are released. Don't cancel one you've already paid for.

Path parameters

FieldTypeDescription
remittance_id*UUIDThe remittance's UUID.

The cancelled remittance.

GET/remittances/{remittance_id}/advice.xlsx

Download the remittance advice

The broker, the insurer, or Insureflow Admin

For a paid remittance: every debit note with its gross premium, broker commission, Squad fee, Insureflow fee, payout fee and the amount paid to the insurer, plus the receipt reference and, per premium, the reference and status of the transfer that carried it.

Path parameters

FieldTypeDescription
remittance_id*UUIDThe remittance's UUID.

Binary .xlsx. 409 if the remittance isn't paid yet.

GET/remittances/template

Download the debit-note Excel template

Broker Admin or Broker Staff

A minimal .xlsx with the column header POST /remittances/resolve-file accepts.

Binary .xlsx (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).

POST/remittances/resolve-file

Match an uploaded Excel file to premiums

Broker Admin or Broker Staff

Upload a spreadsheet of debit notes or premium reference numbers (multipart/form-data); returns which of your unpaid premiums they match, so you can pass the installment_ids to POST /remittances. A reference no premium carries is matched against your policies' debit notes, resolving to that policy's earliest unpaid premium.

Request body

FieldTypeDescription
file*fileThe .xlsx file, multipart/form-data.

Response

FieldTypeDescription
matchedarrayMatched rows, each with the installment_id and amount.
unmatchedarrayRows that didn't match a payable premium, with the reason.

Receipts

Every transfer into a broker's account is recorded as a receipt once Squad confirms it. Most pay a remittance immediately. One that can't be tied to exactly one remittance — an amount no open remittance of that broker's has — waits for the broker, who says which remittance it was for or asks for it back. Refunds are sent only after Insureflow approves them.

GET/receipts

List receipts

Broker, Insurance Company Admin or Insureflow Admin

Brokers see their own. Insurers see only receipts tied to them — paid to their remittances, or that the broker said were meant for them.

Query parameters

FieldTypeDescription
statusstringmatched | awaiting_broker | refund_requested | refund_approved | refunded.
older_than_hoursintegerOnly receipts older than this.
broker_idUUIDNon-broker roles: narrow to one broker.

Response (array item fields)

FieldTypeDescription
id / statusUUID / string
principal_kobo / fee_kobo / settled_kobointegerWhat was sent, Squad's fee, and what arrived.
sender_name / remarksstring | nullWho sent it and the narration.
remittance_id / remittance_reference / matched_byOnce matched: which remittance, and how (amount | reference | broker).
hold_reasonstring | nullWhy it's waiting, in plain words.
candidate_remittancesarrayWhile waiting: your open remittances of exactly this amount.
refundobject | nullThe latest refund request, if any.
POST/receipts/{receipt_id}/apply

Apply a waiting receipt to a remittance

Broker Admin or Broker Staff

The remittance must be yours, open, and for exactly the receipt's amount. If none is, create one that is, then apply. The insurer is paid straight away.

Path parameters

FieldTypeDescription
receipt_id*UUIDThe receipt's UUID.

Request body

FieldTypeDescription
remittance_id*UUIDThe open remittance it paid.

The updated receipt.

POST/receipts/{receipt_id}/refund

Ask for a waiting receipt back

Broker Admin or Broker Staff

Squad verifies the destination account; whether its name matches the sender is shown to the approver. Nothing is sent until an Insureflow Admin approves.

Path parameters

FieldTypeDescription
receipt_id*UUIDThe receipt's UUID.

Request body

FieldTypeDescription
bank_code*stringThe bank's 6-digit NIP code.
account_number*string10 digits.
insurance_company_idUUIDThe insurer the money was meant for, if known — that insurer then sees the receipt.

The updated receipt.

POST/receipts/{receipt_id}/refund/approve

Approve a refund

Insureflow Admin

Queues the transfer back to the broker. A failed refund hands the receipt back to the broker.

Path parameters

FieldTypeDescription
receipt_id*UUIDThe receipt's UUID.

The updated receipt.

POST/receipts/{receipt_id}/refund/reject

Reject a refund

Insureflow Admin

The receipt goes back to waiting for the broker, with the reason.

Path parameters

FieldTypeDescription
receipt_id*UUIDThe receipt's UUID.

Request body

FieldTypeDescription
reason*stringShown to the broker.

The updated receipt.

Payment history (legacy)

Single and bulk payments made before remittances. Read-only: POST /payments and POST /payments/bulk no longer exist — create a remittance instead.

GET/payments

List past single payments

Insurance Company Admin or Broker

Query parameters

FieldTypeDescription
broker_idUUIDInsurer roles only: narrow to one broker.
statusstringsuccess | failed (and older statuses).

Array of payment objects, newest first. GET /payments/{payment_id} returns one.

GET/payments/bulk

List past bulk payments

Insurance Company Admin or Broker

Array of batch objects, newest first. GET /payments/bulk/{batch_id} returns one.

Virtual Accounts

Each broker has one permanent Insureflow account number that every premium is paid into. It's created for the broker automatically when they're approved, from its BVN and phone number (retried every few minutes if the payment provider can't create it at once). A broker an insurer added gives those after signing in, and its account is created then. It's shown on the broker's dashboard under Settings.

GET/virtual-accounts/me

Get your broker's virtual account

Broker Admin or Broker Staff

Response

FieldTypeDescription
broker_id / broker_nameUUID / stringThe owning broker.
squad_va_numberstring | nullThe account number to pay premiums into; null only while it's still being created.
squad_va_bankstring | nullThe account's bank; null while it's being created.
squad_customer_identifierstring | nullThe payment provider's reference for the account.
needs_account_detailsbooleanTrue while there's no account because the broker hasn't given its BVN and phone number yet (PATCH below). Never true on the sandbox.
created_atdatetimeISO 8601 UTC.
PATCH/virtual-accounts/me

Add or update the BVN and phone number

Broker Admin

A broker an insurer added has no BVN or phone number yet, so its admin adds them here; the account number is created straight away. A broker that signed up itself gave them then, and only needs this to correct them. If the payment provider refuses them, nothing is saved and its reason is returned.

Request body

FieldTypeDescription
bvn*stringBank Verification Number, 11 digits.
phone_number*stringContact phone number.

Returns the updated virtual account object.

Commission Configuration

Commission rates are versioned, not constants — changing a rate never edits history in place, it closes the previous row and opens a new one, effective immediately. The rate active at payment time is locked onto that payment permanently.

POST/commission-configs

Set a commission rate

Insurance Company Admin (broker-scoped rates only)

One endpoint handles every scope, but who can call it depends on the request body's scope: global and insurance_company rates are an Insureflow platform operation; an Insurance Company Admin may only set broker-scoped overrides for brokers actively assigned to their own company.

Request body

FieldTypeDescription
scope*string"broker" for an insurer-set override.
broker_id*UUIDRequired when scope is "broker" — must be assigned to your company.
gtbank_rate_bps*integerGTBank fee, in basis points (1 bps = 0.01%).
insureflow_rate_bps*integerInsureflow platform fee, in basis points.
broker_rate_bpsintegerBroker commission, in basis points. Deducted from the insurer's settlement proceeds.

Response

FieldTypeDescription
idUUIDCommission config UUID.
scopestringglobal | insurance_company | broker.
insurance_company_id / broker_idUUID | nullWhichever applies to this row's scope.
gtbank_rate_bps / insureflow_rate_bps / broker_rate_bpsinteger | nullThe three rates.
effective_fromdatetimeWhen this rate became active.
effective_todatetime | nullWhen superseded. null means currently active.
created_byUUIDThe admin who set this rate.
  • Example: a ₦10,000 premium under GTBank 50 bps / Insureflow 50 bps / broker 100 bps splits as ₦50 + ₦50 + ₦100 commission, insurer receives the ₦9,800 remainder.
GET/commission-configs

List commission configurations

Insurance Company Admin or Broker

An Insurance Company Admin sees every config touching their company: global, company-level, and per-broker rates for their assigned brokers. A broker sees global rates and their own broker-scoped rates only.

Array of commission config objects, newest first.

Reminders

Emails the servicing broker about overdue premiums, from you the insurer. Sending a reminder never charges anything.

POST/reminders

Send a reminder

Insurance Company Admin or Staff

Emails the premium's broker a reminder to pay it.

Request body

FieldTypeDescription
installment_id*UUIDMust be due or overdue.

Response

FieldTypeDescription
idUUIDReminder UUID.
installment_id / broker_id / sent_byUUIDWhat was reminded, and by whom.
channelstringAlways "email".
statusstringqueued, then sent once the email goes, or failed if it can't be sent after an hour of retries.
sent_atdatetime | nullWhen the email was sent.
created_atdatetimeISO 8601 UTC.
POST/reminders/bulk

Remind brokers about every overdue premium

Insurance Company Admin or Staff

Creates a reminder for each of your premiums overdue past the 30-day grace period, and emails each broker once, listing all of its premiums with the total. No request body.

Response

FieldTypeDescription
reminders_createdintegerPremiums reminded about.
brokers_emailedintegerBrokers emailed, one email each.
GET/reminders

List reminders

Insurance Company Admin or Broker

Array of reminder objects.

Reports

Premium totals over an explicit date range, grouped by the counterparty on the other side of each transaction — by broker for an insurer's view, by insurer for a broker's.

GET/reports/premiums

Get a premium report

Insurance Company Admin or Broker

Sums paid premiums (remittances and past payments, at gross) in the given range. An insurer's rows are grouped by broker; a broker's rows are grouped by insurer.

Query parameters

FieldTypeDescription
date_from*datetimeISO 8601, inclusive.
date_to*datetimeISO 8601, inclusive.
broker_idUUIDInsurer roles only: narrow to one broker.
insurer_idUUIDBroker roles only: narrow to one insurer.

Response

FieldTypeDescription
rowsarrayOne row per counterparty: counterparty_id, counterparty_name, premiums_processed_kobo, payment_count.
total_kobointegerSum across every row.
date_from / date_todatetimeEchoed back from the request.
GET/reports/premiums/export

Download a premium report as Excel

Insurance Company Admin or Broker

Same params and data as above, returned as a downloadable spreadsheet instead of JSON.

Query parameters

FieldTypeDescription
date_from*datetimeISO 8601, inclusive.
date_to*datetimeISO 8601, inclusive.
broker_idUUIDInsurer roles only.
insurer_idUUIDBroker roles only.

Binary .xlsx (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).

Settlements

Read-only visibility into the payouts Insureflow has made (or attempted) to an insurer's settlement account — one per premium of a paid remittance, sent from Insureflow's Squad balance and narrated with the client's name, policy number and debit note: what the broker sent for it, less fees. Older payouts (one per remittance, single payment or bulk batch) are listed too. In the sandbox no real money moves.

GET/settlements

List settlement payouts

Insurance Company Admin

Query parameters

FieldTypeDescription
statusstringqueued | pending | success | failed.

Response (array item fields)

FieldTypeDescription
idUUIDSettlement payout UUID.
insurance_company_idUUIDThe receiving insurer.
source_typestring"remittance_line" (one premium); "remittance", "payment" or "payment_batch" for older payouts.
source_idUUIDThe remittance line (or older remittance / Payment / PaymentBatch) it pays out.
remittance_idUUID | nullThe remittance the premium was paid in.
narrationstring | nullThe transfer remark, e.g. "ADEBAYO OLUWASEUN POL P-0012 DN DN-55012 IFW-7K3QX".
amount_kobointegerAmount transferred to the insurer.
fee_kobointegerPayout fee deducted before the transfer.
statusstringqueued (not yet sent) | pending (sent, awaiting confirmation) | success | failed.
squad_transfer_refstringServer-generated Squad transfer reference.
attempt_numberinteger1 unless a prior attempt failed and was retried.
previous_attempt_idUUID | nullChains to the attempt this one retried, if any.
failure_reasonstring | nullPopulated only if status is failed.
created_atdatetimeISO 8601 UTC.
GET/settlements/{payout_id}

Get a settlement payout

Insurance Company Admin

Path parameters

FieldTypeDescription
payout_id*UUIDThe payout's UUID.

Same shape as the list response above.

Webhook endpoints

Register HTTPS URLs to receive signed remittance and receipt events — see the Webhooks guide below for events, payloads and signature verification. Endpoints always belong to the caller's own organization; another organization's endpoint returns 404.

POST/webhook-endpoints

Add a webhook endpoint

Insurance Company Admin or Broker Admin

Registers an endpoint and returns its signing secret — the only time the secret is returned. Up to 5 endpoints per organization.

Request body

FieldTypeDescription
url*stringAn https:// URL on a public host, at most 2048 characters.
descriptionstring | nullA label for your own reference.
eventsstring[] | nullAny of remittance.paid, remittance.expired, receipt.awaiting_broker, receipt.refunded, premium.settled, premium.settlement_failed. Omit to receive every event. The legacy payment.succeeded, payment.failed, payment_batch.succeeded and payment_batch.failed are still accepted but no longer sent.

Response

FieldTypeDescription
endpointWebhookEndpointid, url, description, events, is_active, created_at, updated_at.
secretstringThe whsec_… signing secret. Store it now; it can't be retrieved again.
GET/webhook-endpoints

List webhook endpoints

Insurance Company Admin or Broker Admin

Your organization's endpoints. Never includes secrets.

Array of WebhookEndpoint objects.

GET/webhook-endpoints/{endpoint_id}

Get a webhook endpoint

Insurance Company Admin or Broker Admin

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.

A WebhookEndpoint object.

PATCH/webhook-endpoints/{endpoint_id}

Update a webhook endpoint

Insurance Company Admin or Broker Admin

Change any of the fields below; omitted fields are left unchanged. A disabled endpoint receives nothing; its pending deliveries resume when it's re-enabled.

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.

Request body

FieldTypeDescription
urlstringNew https:// URL.
descriptionstringNew label.
eventsstring[]New subscription — at least one event.
is_activebooleanEnable or disable the endpoint.

The updated WebhookEndpoint.

DELETE/webhook-endpoints/{endpoint_id}

Delete a webhook endpoint

Insurance Company Admin or Broker Admin

Stops all deliveries to the endpoint and deletes its delivery history.

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.

HTTP 204 No Content.

POST/webhook-endpoints/{endpoint_id}/rotate-secret

Rotate the signing secret

Insurance Company Admin or Broker Admin

Issues a new secret. The old one stops signing immediately, including for retries already queued.

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.

Response

FieldTypeDescription
endpointWebhookEndpointThe endpoint.
secretstringThe new whsec_… secret, shown only once.
POST/webhook-endpoints/{endpoint_id}/test

Send a test event

Insurance Company Admin or Broker Admin

Queues a webhook.test event to this endpoint only, regardless of its subscriptions. It's sent within about 15 seconds, signed like any other event.

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.

HTTP 202 with the queued WebhookDelivery.

GET/webhook-endpoints/{endpoint_id}/deliveries

List recent deliveries

Insurance Company Admin or Broker Admin

The 50 most recent deliveries to this endpoint, newest first.

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.

Query parameters

FieldTypeDescription
statusstringpending | succeeded | failed.

Response (array item fields)

FieldTypeDescription
idUUIDDelivery UUID — use it to redeliver.
event_idUUIDThe event's id, as sent in Insureflow-Event-Id.
event_typestringe.g. remittance.paid.
statusstringpending | succeeded | failed.
attempt_countintegerAttempts made so far.
next_attempt_atdatetime | nullWhen the next retry is due, while pending.
last_response_statusinteger | nullYour endpoint's last HTTP status, if it responded.
last_errorstring | nullWhy the last attempt failed.
delivered_atdatetime | nullWhen a 2xx was received.
payloadobjectThe exact event body sent.
POST/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/redeliver

Redeliver an event

Insurance Company Admin or Broker Admin

Sends the same event — same id, same body — again, with a fresh retry schedule.

Path parameters

FieldTypeDescription
endpoint_id*UUIDThe endpoint's UUID.
delivery_id*UUIDThe delivery's UUID.

HTTP 202 with the re-queued WebhookDelivery.

Webhooks

Webhooks let your system hear about payments the moment they happen, instead of polling. When a remittance you're party to is paid, or money needs a broker's attention, Insureflow sends an HTTPS POSTwith a JSON body to every endpoint you've registered.

  • Insurance companies receive events for remittances paying their premiums.
  • Brokers receive events for their own remittances and the money arriving in their account.

Register up to 5 endpoints per organization, in the dashboard under Settings → Webhooks or through the Webhook endpoints API. Sandbox and production are separate: register sandbox endpoints against the sandbox API, using your sandbox API key.

Events

Each endpoint can subscribe to any subset of these; by default it receives all of them.

EventSent when
remittance.paidA broker's transfer paid a remittance: every premium in it is paid and one transfer per premium is on its way to the insurer (each premium carries its squad_transfer_ref). Insurer and broker.
remittance.expiredAn open remittance was never paid and its premiums were released. Broker only.
receipt.awaiting_brokerMoney arrived that couldn't be matched to exactly one remittance; the broker needs to say what it was for, or ask for it back. Broker only.
receipt.refundedUnmatched money was sent back to the broker. Broker, and the insurer it was meant for if the broker said.
premium.settledOne premium's own transfer reached the insurer — data.premium (debit note, policy number, remittance reference) and data.settlement (amount, squad_transfer_ref, narration). Insurer and broker.
premium.settlement_failedOne premium's transfer failed after every automatic retry and needs Insureflow to retry it. Insurer and broker.
webhook.testYou clicked Send test event, or called POST /webhook-endpoints/{id}/test.

Payload

Every event has the same envelope:

remittance.paid
{
  "id": "7d0c6c1e-5b8e-4a5e-9d0b-2f4f1c9a8e21",
  "type": "remittance.paid",
  "created_at": "2026-10-05T10:15:30Z",
  "environment": "sandbox",
  "data": {
    "remittance": {
      "id": "3f6b8a52-0e1d-4d7a-9a51-6c0f2b7d9e10",
      "reference": "IFW-7K3QX",
      "status": "paid",
      "broker_id": "d3b2a1c0-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
      "insurance_company_id": "e4c3b2a1-0f9e-4d8c-8b7a-6f5e4d3c2b1a",
      "paid_at": "2026-10-05T10:15:29Z",
      "matched_by": "amount",
      "received_kobo": 4500000,
      "squad_fee_kobo": 11250,
      "insureflow_fee_kobo": 22500,
      "payout_fee_kobo": 0,
      "insurer_net_kobo": 4466250,
      "premiums": [
        {
          "installment_id": "b1f0c7d4-9a2e-4c55-8f3e-1d2a3b4c5d6e",
          "policy_id": "c2a1e8f7-6b5d-4c3e-9f1a-0b2c3d4e5f60",
          "debit_note": "DN-2026-0001",
          "policy_number": "POL/MTR/2026/0001",
          "gross_kobo": 5000000,
          "broker_commission_kobo": 500000,
          "net_kobo": 4500000,
          "squad_fee_kobo": 11250,
          "insureflow_fee_kobo": 22500,
          "payout_fee_kobo": 0,
          "insurer_net_kobo": 4466250,
          "settlement_payout_id": "a7e5d3c1-2b4f-4e6a-8c0d-1f3e5a7c9b2d",
          "squad_transfer_ref": "SBN1EBZEQ8_2f1c0d9e8b7a6f5e4d3c2b1a09f8e7d6",
          "narration": "ADEBAYO OLUWASEUN POL POL/MTR/2026/0001 DN DN-2026-0001 IFW-7K3QX"
        }
      ]
    }
  }
}

premiums lists every premium the transfer paid, with its debit note and each fee, so you can reconcile without calling back. receipt.* events carry data.receipt(amount, sender, narration and why it's waiting); remittance.expired carries data.remittance with its reference and total. Endpoints created before remittances may still list the old payment.* events; nothing sends those any more.

Amounts are always integer kobo. environment is sandbox or production, so a misrouted test event can never be mistaken for a live one.

Headers

HeaderValue
Content-Typeapplication/json
Insureflow-Event-IdThe event's id. The same across retries — de-duplicate on it.
Insureflow-Event-TypeThe event's type.
Insureflow-Signaturet=<unix timestamp>,v1=<signature>

Verifying signatures

When you add an endpoint you receive a signing secret (whsec_…), shown only once. Every request is signed with it: v1 is the hex-encoded HMAC-SHA256 of "<t>.<raw request body>", keyed with your secret. Always verify before trusting a request:

  1. Use the raw request body, exactly as received. Re-serializing parsed JSON changes the bytes.
  2. Reject requests whose t is more than 5 minutes old, to block replays.
  3. Compare signatures in constant time.
Python
import hashlib
import hmac
import time

def verify_insureflow_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    timestamp, signature = parts.get("t", ""), parts.get("v1", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:
        return False
    signed = timestamp.encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
Node.js
const crypto = require("crypto");

function verifyInsureflowSignature(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.`)
    .update(rawBody)
    .digest("hex");
  const given = String(parts.v1 || "");
  return given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

If you rotate the secret, the old one stops signing immediately. Deploy the new secret before rotating, or accept either during the switch.

Responding and retries

Respond with any 2xx status within 10 seconds, and do the real work after responding (for example by queueing it). Anything else counts as a failure: another status code, a redirect, or a timeout.

Failed deliveries are retried with the same event id and body about 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours after the previous attempt — 8 attempts over roughly two days — after which the delivery is marked failed. You can see every delivery and resend any of them from Settings → Webhooks, or with POST /webhook-endpoints/{id}/deliveries/{delivery_id}/redeliver.

Best practices

  • Expect duplicates. Delivery is at-least-once, so record each Insureflow-Event-Id you've processed and skip repeats.
  • Don't rely on order. Retries can deliver an older event after a newer one. If you need the current state, fetch it: GET /remittances/{id} or GET /receipts/{id}.
  • Use HTTPS. Insureflow only delivers to https:// URLs on public addresses.
  • Test first. Use Send test event to confirm your endpoint and signature check work before going live.

Integration Flows

Two typical end-to-end flows, reflecting who is actually authorized to call each step.

A. An insurance company onboards a policyholder and writes a policy

  1. Insurer and broker both complete onboarding (POST /insurance-companies, POST /brokers) and are approved by Insureflow; both activate with their one-time token.
  2. The insurer adds the brokers it works with (GET /insurance-companies/me/broker-directory, then POST /insurance-companies/me/brokers).
  3. The insurer sets its settlement account (POST /insurance-companies/{company_id}/settlement-account).
  4. The insurer creates the policy, naming the servicing broker, with its client's details in policyholder— or an existing client's policyholder_id (POST /policies). Its one premium (premium_amount_kobo, for the whole duration) is split into equal payments by frequency automatically: ₦120,000 over a year monthly is 12 payments of ₦10,000.

B. A broker collects a premium

  1. List due or overdue installments for the policyholder (GET /installments?status=due).
  2. Create a remittance for one premium or many (POST /remittances) with a fresh Idempotency-Key. All its premiums must be for one insurer.
  3. Transfer exactly pay_to.amount_kobo into pay_to.virtual_account_number— your permanent Insureflow account. The amount is what identifies the remittance, so the narration is optional (you can't hold two open remittances of the same total).
  4. Wait for a remittance.paid webhook, or poll GET /remittances/{id} until its status leaves open. Once paid, every premium is marked paid and the insurer is paid at once — one transfer per premium, each announced by a premium.settled webhook.
  5. If you sent a different amount, you'll get receipt.awaiting_broker: apply the money to the right remittance (POST /receipts/{id}/apply) or ask for it back (POST /receipts/{id}/refund).

Testing in the Sandbox

The sandbox runs the whole payment flow end to end with no real money: you write policies as the insurer, pay them as the broker, and see exactly what the insurer would receive. You can do it from the sandbox dashboard, exactly as it works in production, or through the API.

From the dashboard

  1. On the sandbox site, sign up a test insurer (/onboard/insurer), sign in with the one-time password and set your own. On the sandbox it's approved straight away, with a placeholder settlement account you can replace in Settings.
  2. In a private window, sign up a test broker (/onboard/broker) the same way. It's approved straight away and gets its Insureflow account number.
  3. As the insurer, add the broker under Brokers, then create a policy for it.
  4. As the broker, open Policies, select the due premiums and press Pay. On the remittance page, press Simulate this transfer.
  5. As the insurer, open Remittances and Settlements: the premiums are paid, with one payout per premium and the remittance advice to download.

A “Getting started” checklist on each dashboard ticks these steps off as you go. In production, Insureflow reviews and approves each insurer and broker, and settlement accounts are verified with the bank.

Through the API, use the sandbox base URL and send your keys as X-API-Key. Your insurer key and broker key belong to an insurer and a test broker that already work together; the broker already has its Insureflow account number and the insurer a placeholder settlement account.

1. Write a policy (insurer key)

  1. Optional: register a webhook endpoint (POST /webhook-endpoints) and store the secret it returns.
  2. Create a policy for the test broker with its client's details (POST /policies with policyholder: { full_name, email }), e.g. premium_amount_kobo: 10000000 for a ₦100,000 premium with duration_months: 12 — or upload many at once with POST /policies/import.
  3. List its premiums (GET /policies/{id}/installments) and note their ids.

2. Pay it (broker key)

  1. Create a remittance for one or more of those premiums (POST /remittances with an Idempotency-Key). The response shows each premium's gross, broker commission and net, the exact total to send, and the reference.
  2. Simulate the transfer (POST /remittances/{id}/simulate-transfer). It comes back paid.

3. See what the insurer receives (insurer key)

  1. GET /remittances/{id}: each line now shows the bank's collection fee, Insureflow's 0.5% and the insurer's net.
  2. GET /remittances/{id}/advice.xlsx: the remittance advice, one row per debit note, to reconcile against your books.
  3. GET /settlements: one payout per premium to your settlement account, each narrated with the client's name, policy number and debit note.
  4. GET /policies/{id}/installments: those premiums are now paid.
  5. Your endpoint receives remittance.paid, then one premium.settled per premium (see GET /webhook-endpoints/{id}/deliveries).

4. Try the edge cases (broker key)

  • Wrong amount.Send a different amount to the broker's account with the payment simulator: POST /payments/simulate {"account_number": "<pay_to.virtual_account_number>", "amount_naira": 1234.56}. Within a few minutes it appears in GET /receipts awaiting the broker, and the remittance stays open. Apply it to a remittance of that exact amount (POST /receipts/{id}/apply) or ask for it back (POST /receipts/{id}/refund; Insureflow approves refunds).
  • Same amount twice. Creating a second open remittance with the same total as one you already have returns 409: the amount alone identifies which remittance a transfer pays, so pay or cancel the first one.
  • Cancel. POST /remittances/{id}/cancel releases the premiums. An unpaid remittance also expires after 14 days.

Sandbox vs live

StepSandbox vs live
Broker's transferSandbox: POST /remittances/{id}/simulate-transfer has the bank's simulator send the exact amount. Live: a real bank transfer from the broker's bank for the exact amount — no narration needed.
Payout to the insurerOne transfer per premium from Insureflow's Squad balance, narrated with the client's name, policy number and debit note. Sandbox: Squad's sandbox transfers (or, if the sandbox balance can't cover them, recorded without moving money). Live: real transfers to your verified settlement account.
Sign-upSandbox: insurers and brokers are approved as soon as they sign up. Live: Insureflow reviews and approves each one.
Settlement accountSandbox: a placeholder is set for you, or enter any account — it's saved as typed. Live: your own account, verified by name before it's saved.
Data and keysSeparate. Nothing in the sandbox carries over to live.

Appendix

NIP bank codes

6-digit institution codes accepted by bank_code fields — not the 3-digit CBN code — for Nigeria's commercial and merchant banks, as listed by Squad. The dashboard fills these in for you when you pick a bank by name.

CodeBank
000014Access Bank
000009Citibank
060001Coronation Merchant Bank
000010Ecobank
060002FBNQuest Merchant Bank
000003FCMB
000007Fidelity Bank
000016First Bank of Nigeria
000027Globus Bank
060004Greenwich Merchant Bank
000013GTBank
000006Jaiz Bank
000002Keystone Bank
000029Lotus Bank
060003Nova Merchant Bank
000036Optimus Bank
000008Polaris Bank
000031PremiumTrust Bank
000023Providus Bank
000024Rand Merchant Bank
000034Signature Bank
000012Stanbic IBTC Bank
000021Standard Chartered
000001Sterling Bank
000022SunTrust Bank
000026Taj Bank
000025Titan Trust Bank
000004UBA (United Bank for Africa)
000018Union Bank
000011Unity Bank
000017Wema Bank
000015Zenith Bank

Status values quick reference

dueoverduepaidcancelledinitiatedsuccessmismatchexpiredfailedpendingapprovedrejectedsuspended