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.
| Role | Scope |
|---|---|
| Insurance Company Admin | Manages one insurance company: creates policyholders and policies, assigns servicing brokers, sets the settlement account, and reads everything under that company. |
| Broker Admin | Manages one brokerage: creates staff logins and has full transacting access — collecting premiums, viewing reports, sending reminders. |
| Broker Staff | Same 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": "..." }.
| Status | Meaning |
|---|---|
| 400 | Bad request — malformed input. |
| 401 | Missing or invalid Bearer token or API key (including an expired or revoked sandbox key). |
| 403 | Authenticated, but your role lacks the required permission. |
| 404 | The resource doesn't exist, or doesn't belong to your organization. |
| 409 | Conflict — e.g. the Idempotency-Key was already used with a different request body. |
| 422 | Validation error — see detail for the specific field. |
| 429 | Rate limit exceeded — back off and retry. |
| 502 | Upstream 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.
/auth/activateActivate a new account
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
| Field | Type | Description |
|---|---|---|
| token* | string | The one-time activation token. |
| password* | string | Chosen password. |
HTTP 204 No Content on success.
/auth/loginObtain a Bearer token
Standard OAuth2 password flow. Sends credentials as application/x-www-form-urlencoded form data, not JSON.
Request body
| Field | Type | Description |
|---|---|---|
| username* | string | Account email address. |
| password* | string | Account password. |
Response
| Field | Type | Description |
|---|---|---|
| access_token | string | JWT Bearer token, valid 60 minutes. |
| token_type | string | Always "bearer". |
- Login is blocked while your organization (insurance company or broker) is pending, rejected, or suspended.
/auth/change-passwordChange your own password
Changes the password of the currently authenticated account.
Request body
| Field | Type | Description |
|---|---|---|
| current_password* | string | Current password, for verification. |
| new_password* | string | New password. |
Response
| Field | Type | Description |
|---|---|---|
| access_token | string | A fresh Bearer token (the old one stays valid until it expires). |
| token_type | string | Always "bearer". |
/auth/forgot-passwordEmail a password-reset link
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
| Field | Type | Description |
|---|---|---|
| email* | string | The 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.
/auth/reset-passwordSet a new password from a reset link
Sets the password using the token from the emailed link, then the token stops working.
Request body
| Field | Type | Description |
|---|---|---|
| token* | string | The token from the reset link. |
| new_password* | string | At 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.
/insurance-companies/me/staffGive a staff member a login
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
| Field | Type | Description |
|---|---|---|
| email* | string | The staff member's email; must not already have an account. |
Response
| Field | Type | Description |
|---|---|---|
| otp | string | Their one-time password, also emailed. Shown once. |
/insurance-companies/me/staffList your company's logins
Every login of your company, the admin first.
Response
| Field | Type | Description |
|---|---|---|
| id / email / role | string | insurance_company_admin or insurance_company_staff. |
| is_active | boolean | False once deactivated. |
| invite_pending | boolean | True until they've signed in and set their own password. |
/insurance-companies/me/staff/{user_id}Deactivate a staff member
Ends a staff member's access immediately. The admin login can't be deactivated.
Path parameters
| Field | Type | Description |
|---|---|---|
| user_id* | UUID | The staff login's id. |
/insurance-companies/me/brokers/onboardAdd a new broker you work with
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
| Field | Type | Description |
|---|---|---|
| name* | string | The broker's business name. |
| contact_email* | string | Its admin's email; must not already have an account. |
| bvn | string | Optional: 11 digits. Given with phone_number, the account number is created at once instead of when the broker adds them. |
| phone_number | string | Optional, with bvn. |
Response
| Field | Type | Description |
|---|---|---|
| broker | object | The broker, approved, with approved_for_insurer_id set to you. |
| otp | string | Its admin's one-time password, also emailed. Shown once. |
/insurance-companies/me/brokers/import/previewCheck a bulk broker upload
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
| Field | Type | Description |
|---|---|---|
| total_rows | integer | Non-blank rows in the file. |
| ready | BrokerImportRow[] | row_number, name and contact_email of each broker that would be added. |
| errors | BrokerImportRowError[] | row_number, contact_email and reason for each that wouldn't. |
| file_error | string | null | Why the whole file couldn't be read, e.g. a missing column. |
/insurance-companies/me/brokers/importAdd brokers in bulk
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
| Field | Type | Description |
|---|---|---|
| rows* | BrokerImportRow[] | The preview's ready rows. |
Response
| Field | Type | Description |
|---|---|---|
| created_count | integer | Brokers added. |
| created | object[] | broker_id, name, contact_email and otp (its admin's one-time password, also emailed; shown once) of each. |
| errors | BrokerImportRowError[] | Rows not added, and why. |
/insurance-companies/me/broker-requestsList brokers asking to work with you
Brokers waiting for approval that named you when they signed up (requested_insurer_id).
Response
| Field | Type | Description |
|---|---|---|
| [] | Broker | Pending brokers, oldest first. |
/insurance-companies/me/broker-requests/{broker_id}/approveApprove a broker's request
Approves the broker for you only, creates its account number, and adds it to your brokers.
Path parameters
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | The requesting broker. |
/insurance-companies/me/broker-requests/{broker_id}/declineDecline a broker's request
Not a broker you work with. It isn't rejected: it waits for Insureflow to review it instead.
Path parameters
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | The requesting broker. |
/insurance-companies/me/broker-directorySearch brokers you can work with
Insureflow-approved brokers, searchable by name or email, each flagged with whether it's already one of yours.
Query parameters
| Field | Type | Description |
|---|---|---|
| q | string | Matches name or email, case-insensitive. |
| limit | integer | Up to 100 (default 50). |
Response (array item fields)
| Field | Type | Description |
|---|---|---|
| id / name / contact_email | The broker. | |
| assigned | boolean | Already working with you. |
/insurance-companies/me/brokersAdd a broker
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
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | From the broker directory. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | The assignment. |
| broker_id / insurance_company_id | UUID | |
| is_active / assigned_at / ended_at |
- 409 if the broker isn't approved; 404 if it doesn't exist.
/insurance-companies/me/brokers/{broker_id}Remove a broker
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
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | The broker's UUID. |
The ended assignment.
/insurance-companiesSubmit onboarding application
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
| Field | Type | Description |
|---|---|---|
| name* | string | Legal company name. |
| contact_email* | string | Primary admin contact email. |
Response
| Field | Type | Description |
|---|---|---|
| company | InsuranceCompanyOut | The created company record (see GET below for its full shape). |
| otp | string | One-time activation token — only ever returned here. Store or relay it immediately. |
/insurance-companies/{company_id}Get company details
Returns the current state of the insurance company record.
Path parameters
| Field | Type | Description |
|---|---|---|
| company_id* | UUID | The company's UUID. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Company UUID. |
| name | string | Legal name. |
| contact_email | string | Admin contact email. |
| status | string | pending | approved | rejected | suspended. |
| rejection_reason | string | null | Populated if rejected. |
| settlement_bank_code | string | null | null until a settlement account is set. |
| settlement_account_number | string | null | null until a settlement account is set. |
| settlement_account_name | string | null | null until a settlement account is set. |
| parent_company_id | UUID | null | Set only for a subsidiary under a parent organization grouping. |
| created_at | datetime | ISO 8601 UTC. |
/insurance-companies/{company_id}/brokersList brokers assigned to this company
Every broker with an active assignment to this insurer — the many-to-many relationship, not a fixed 1:1 broker.
Path parameters
| Field | Type | Description |
|---|---|---|
| company_id* | UUID | The company's UUID. |
Array of broker objects (same shape as the Brokers section's GET response).
/insurance-companies/{company_id}/settlement-accountSet settlement bank account
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
| Field | Type | Description |
|---|---|---|
| company_id* | UUID | The company's UUID. |
Request body
| Field | Type | Description |
|---|---|---|
| bank_code* | string | 6-digit NIP institution code, e.g. GTBank = 000013. Not the 3-digit CBN code — see the Appendix. |
| account_number* | string | 10-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.
/brokersSubmit broker onboarding application
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
| Field | Type | Description |
|---|---|---|
| name* | string | Legal broker organization name. |
| contact_email* | string | Primary admin contact email. |
| bvn* | string | 11-digit Bank Verification Number, needed to create the broker's account. Not needed on the sandbox, which uses a test BVN. |
| phone_number* | string | Contact phone number, needed to create the broker's account. Not needed on the sandbox. |
Response
| Field | Type | Description |
|---|---|---|
| broker | BrokerOut | The created broker record. |
| otp | string | One-time activation token — only ever returned here. |
/brokers/{broker_id}Get broker details
Broker roles can only retrieve their own broker record.
Path parameters
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | The broker's UUID. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Broker UUID. |
| name | string | Broker name. |
| contact_email | string | Admin contact email. |
| status | string | pending | approved | rejected | suspended. |
| rejection_reason | string | null | Populated if rejected. |
| squad_va_number | string | null | The broker's own collection virtual account number, once provisioned. |
| squad_va_bank | string | null | Bank the virtual account is held at. |
| created_at | datetime | ISO 8601 UTC. |
/brokers/{broker_id}/insurersList insurers this broker works with
Every insurer with an active assignment to this broker.
Path parameters
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | The broker's UUID. |
Array of insurance company objects (same shape as the Insurance Companies section's GET response).
/brokers/me/teamList your team
Every login of your broker, admins first.
Response (array item fields)
| Field | Type | Description |
|---|---|---|
| id / email / role | UUID / string / string | role is broker_admin or broker_staff. |
| is_active | boolean | False once deactivated. |
| invite_pending | boolean | True until they've signed in and chosen a password. |
| is_you | boolean | Your own login, whose role and access you can't change. |
/brokers/me/teamAdd someone to your team
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
| Field | Type | Description |
|---|---|---|
| email* | string | Must not already have an account. |
| role | string | broker_staff (default) or broker_admin. |
Response
| Field | Type | Description |
|---|---|---|
| otp | string | Their one-time password, also emailed. Shown once. |
/brokers/me/team/{user_id}Change a team member's role
Makes them an admin or staff, from their next request. Not your own login, so your broker always keeps an admin.
Path parameters
| Field | Type | Description |
|---|---|---|
| user_id* | UUID | The team member. |
Request body
| Field | Type | Description |
|---|---|---|
| role* | string | broker_admin or broker_staff. |
Returns the team member, as in the list.
/brokers/me/team/{user_id}Deactivate a team member
Ends their access at once. Not your own login.
Path parameters
| Field | Type | Description |
|---|---|---|
| user_id* | UUID | The 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.
/usersCreate a policyholder
full_name is the only required field; the rest improve reminder delivery and identity verification.
Request body
| Field | Type | Description |
|---|---|---|
| broker_id* | UUID | The broker who will service this policyholder — must have an active assignment to your company. |
| full_name* | string | Full legal name. |
| string | For payment reminders. | |
| phone_number | string | Local format, e.g. 08012345678. |
| identification_number | string | Government ID (NIN, passport, etc.) — encrypted at rest. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Policyholder UUID — required to create a policy. |
| broker_id | UUID | The servicing broker. |
| insurance_company_id | UUID | The underwriting company. |
| broker_name | string | Denormalized broker name, for display. |
| insurance_company_name | string | Denormalized company name, for display. |
| full_name | string | As provided. |
| string | null | null if not provided. | |
| phone_number | string | null | null if not provided. |
| identification_number | string | null | null if not provided. Masked in most UI surfaces, plaintext over the API to the owning tenant. |
| created_at | datetime | ISO 8601 UTC. |
/users/{policyholder_id}Get a policyholder
Path parameters
| Field | Type | Description |
|---|---|---|
| policyholder_id* | UUID | The policyholder's UUID. |
Same shape as the create response above.
/usersList policyholders
An Insurance Company Admin sees every policyholder across their assigned brokers by default; a broker sees only their own.
Query parameters
| Field | Type | Description |
|---|---|---|
| broker_id | UUID | Narrow 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.
/policiesCreate a policy
Request body
| Field | Type | Description |
|---|---|---|
| policyholder | object | A 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_id | UUID | An existing policyholder of this broker, instead of policyholder. |
| broker_id* | UUID | The broker servicing this policy — must have an active assignment to your company. |
| reference_number* | string | The debit note — unique across all policies. A renewal or endorsement gets a new one. |
| policy_number* | string | Your 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* | integer | The 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* | string | How the premium is paid: monthly | quarterly | annually. |
| start_date* | date | ISO 8601 (YYYY-MM-DD). The first premium is due on this date. |
| policy_type | string | Open string label. Defaults to GENERIC — any value is accepted (LIFE, MOTOR, HEALTH, ...). |
| policy_name | string | Optional display name for the policy. |
| duration_months* | integer | The policy's term in months, a whole number of years: 12, 24 … 120. The first payment is due on start_date. |
| coverage_amount_kobo | integer | Optional sum insured, in kobo. |
| coverage_items | string | Optional free-text coverage detail. |
| beneficiaries | string | Optional free-text beneficiary list. |
| broker_notes | string | Optional notes visible to the servicing broker. |
| internal_tags | string | Optional comma-separated tags. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Policy UUID — required to list installments. |
| policyholder_id / broker_id / insurance_company_id | UUID | The three parties. |
| reference_number | string | The debit note. |
| policy_number | string | null | Your policy number. null only for policies created before policy numbers were introduced. |
| policy_type | string | Type label. |
| premium_amount_kobo | integer | The premium for the whole duration, in kobo (when premium_is_total). |
| premium_is_total | boolean | true: 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_frequency | string | How the premium is paid: monthly | quarterly | annually. |
| duration_months / payment_count | integer | The term in months, and how many payments it's paid in (12 months monthly: 12). |
| start_date | date | Policy start date — the first payment's due date. |
| status | string | active | cancelled. |
| policyholder_name / broker_name / insurance_company_name | string | Denormalized names, for display. |
| created_at | datetime | ISO 8601 UTC. |
/policies/{policy_id}Get a policy
Path parameters
| Field | Type | Description |
|---|---|---|
| policy_id* | UUID | The policy's UUID. |
Same shape as the create response above.
/policiesList policies
user_id narrows to one policyholder's policies; omitted returns every policy you can see.
Query parameters
| Field | Type | Description |
|---|---|---|
| user_id | UUID | Narrow to one policyholder. |
Array of policy objects.
/policies/{policy_id}/installmentsList a policy's installments
The full installment schedule for one policy, in due-date order.
Path parameters
| Field | Type | Description |
|---|---|---|
| policy_id* | UUID | The policy's UUID. |
Array of installment objects — see the Installments section for the full field list.
/policies/import/templateDownload the bulk-import template
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).
/policies/import/previewPreview a bulk policy import
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
| Field | Type | Description |
|---|---|---|
| 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
| Field | Type | Description |
|---|---|---|
| total_rows | integer | Non-blank data rows in the file. |
| ready | PolicyImportRow[] | 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_error | string | null | Set when the file itself is unusable (e.g. a required column is missing). |
/policies/importImport policies in bulk
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
| Field | Type | Description |
|---|---|---|
| rows* | PolicyImportRow[] | The ready rows from the preview, up to 500. |
Response
| Field | Type | Description |
|---|---|---|
| created_count | integer | Policies created. |
| policy_ids | UUID[] | 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.
/installmentsList installments
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
| Field | Type | Description |
|---|---|---|
| status | string | due | overdue | paid | cancelled. |
| policy_type | string | Filter by the policy's type label. |
| ids | string | Comma-separated installment UUIDs — fetch an exact set in one call. |
| q | string | Case-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_id | UUID | Broker roles only: narrow to one insurer. |
Response (array item fields)
| Field | Type | Description |
|---|---|---|
| id | UUID | Installment UUID — the value POST /remittances takes. |
| policy_id | UUID | The parent policy. |
| due_date | date | When this installment is due. |
| amount_kobo | integer | Amount owed, in kobo. |
| status | string | due | overdue | paid | cancelled. |
| payment_id | UUID | null | Set once paid via a single payment. |
| payment_batch_item_id | UUID | null | Set once paid via a bulk batch. Mutually exclusive with payment_id. |
| reference_number | string | null | Your own per-installment reference, if set. |
| policy_reference_number | string | null | The parent policy's debit note. |
| policy_number | string | null | The parent policy's policy number. |
| policy_type | string | Denormalized from the parent policy. |
| policyholder_name / broker_name / insurance_company_name | string | Denormalized names, for display. |
| payment_date | datetime | null | When this installment was actually paid — null until then. Distinct from settlement date. |
| created_at | datetime | ISO 8601 UTC. |
/installments/{installment_id}/reference-numberSet an installment's reference number
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
| Field | Type | Description |
|---|---|---|
| installment_id* | UUID | The installment's UUID. |
Request body
| Field | Type | Description |
|---|---|---|
| reference_number | string | null | Pass 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%.
/remittancesCreate a remittance
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
| Field | Type | Description |
|---|---|---|
| installment_ids* | UUID[] | 1–500 installment UUIDs. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Remittance UUID. |
| reference | string | e.g. IFW-7K3QX — optional in the transfer narration; it appears on the insurer's transfers. |
| status | string | open | paid | cancelled | expired. |
| total_net_kobo | integer | The exact amount to transfer: each premium's gross less your broker commission. |
| pay_to | object | null | While open: virtual_account_number, virtual_account_bank, amount_kobo, reference, expires_at. |
| same_total_warning | boolean | Only 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_status | string | null | Once paid: in_progress | settled | payout_failed — the state of the per-premium transfers to the insurer. |
| premiums_settled | integer | How many premiums' transfers have reached the insurer. |
| lines | array | One 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_at | datetime | An 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.
/remittances/{remittance_id}Get a remittance
Poll this until status leaves open.
Path parameters
| Field | Type | Description |
|---|---|---|
| remittance_id* | UUID | The remittance's UUID. |
Same shape as the create response above.
/remittancesList remittances
Scoped to what you can see: your own as a broker, those for your company as an insurer.
Query parameters
| Field | Type | Description |
|---|---|---|
| status | string | open | paid | cancelled | expired. |
| broker_id / insurer_id | UUID | Narrow further. |
| limit | integer | Up to 200 (default 50). |
Array of remittance objects, newest first.
/remittances/{remittance_id}/simulate-transferSimulate the broker's transfer (sandbox only)
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
| Field | Type | Description |
|---|---|---|
| remittance_id* | UUID | An 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.
/remittances/{remittance_id}/cancelCancel a remittance
Only an open remittance; its premiums are released. Don't cancel one you've already paid for.
Path parameters
| Field | Type | Description |
|---|---|---|
| remittance_id* | UUID | The remittance's UUID. |
The cancelled remittance.
/remittances/{remittance_id}/advice.xlsxDownload the remittance advice
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
| Field | Type | Description |
|---|---|---|
| remittance_id* | UUID | The remittance's UUID. |
Binary .xlsx. 409 if the remittance isn't paid yet.
/remittances/templateDownload the debit-note Excel template
A minimal .xlsx with the column header POST /remittances/resolve-file accepts.
Binary .xlsx (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).
/remittances/resolve-fileMatch an uploaded Excel file to premiums
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
| Field | Type | Description |
|---|---|---|
| file* | file | The .xlsx file, multipart/form-data. |
Response
| Field | Type | Description |
|---|---|---|
| matched | array | Matched rows, each with the installment_id and amount. |
| unmatched | array | Rows 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.
/receiptsList receipts
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
| Field | Type | Description |
|---|---|---|
| status | string | matched | awaiting_broker | refund_requested | refund_approved | refunded. |
| older_than_hours | integer | Only receipts older than this. |
| broker_id | UUID | Non-broker roles: narrow to one broker. |
Response (array item fields)
| Field | Type | Description |
|---|---|---|
| id / status | UUID / string | |
| principal_kobo / fee_kobo / settled_kobo | integer | What was sent, Squad's fee, and what arrived. |
| sender_name / remarks | string | null | Who sent it and the narration. |
| remittance_id / remittance_reference / matched_by | Once matched: which remittance, and how (amount | reference | broker). | |
| hold_reason | string | null | Why it's waiting, in plain words. |
| candidate_remittances | array | While waiting: your open remittances of exactly this amount. |
| refund | object | null | The latest refund request, if any. |
/receipts/{receipt_id}/applyApply a waiting receipt to a remittance
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
| Field | Type | Description |
|---|---|---|
| receipt_id* | UUID | The receipt's UUID. |
Request body
| Field | Type | Description |
|---|---|---|
| remittance_id* | UUID | The open remittance it paid. |
The updated receipt.
/receipts/{receipt_id}/refundAsk for a waiting receipt back
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
| Field | Type | Description |
|---|---|---|
| receipt_id* | UUID | The receipt's UUID. |
Request body
| Field | Type | Description |
|---|---|---|
| bank_code* | string | The bank's 6-digit NIP code. |
| account_number* | string | 10 digits. |
| insurance_company_id | UUID | The insurer the money was meant for, if known — that insurer then sees the receipt. |
The updated receipt.
/receipts/{receipt_id}/refund/approveApprove a refund
Queues the transfer back to the broker. A failed refund hands the receipt back to the broker.
Path parameters
| Field | Type | Description |
|---|---|---|
| receipt_id* | UUID | The receipt's UUID. |
The updated receipt.
/receipts/{receipt_id}/refund/rejectReject a refund
The receipt goes back to waiting for the broker, with the reason.
Path parameters
| Field | Type | Description |
|---|---|---|
| receipt_id* | UUID | The receipt's UUID. |
Request body
| Field | Type | Description |
|---|---|---|
| reason* | string | Shown 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.
/paymentsList past single payments
Query parameters
| Field | Type | Description |
|---|---|---|
| broker_id | UUID | Insurer roles only: narrow to one broker. |
| status | string | success | failed (and older statuses). |
Array of payment objects, newest first. GET /payments/{payment_id} returns one.
/payments/bulkList past bulk payments
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.
/virtual-accounts/meGet your broker's virtual account
Response
| Field | Type | Description |
|---|---|---|
| broker_id / broker_name | UUID / string | The owning broker. |
| squad_va_number | string | null | The account number to pay premiums into; null only while it's still being created. |
| squad_va_bank | string | null | The account's bank; null while it's being created. |
| squad_customer_identifier | string | null | The payment provider's reference for the account. |
| needs_account_details | boolean | True 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_at | datetime | ISO 8601 UTC. |
/virtual-accounts/meAdd or update the BVN and phone number
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
| Field | Type | Description |
|---|---|---|
| bvn* | string | Bank Verification Number, 11 digits. |
| phone_number* | string | Contact 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.
/commission-configsSet a commission rate
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
| Field | Type | Description |
|---|---|---|
| scope* | string | "broker" for an insurer-set override. |
| broker_id* | UUID | Required when scope is "broker" — must be assigned to your company. |
| gtbank_rate_bps* | integer | GTBank fee, in basis points (1 bps = 0.01%). |
| insureflow_rate_bps* | integer | Insureflow platform fee, in basis points. |
| broker_rate_bps | integer | Broker commission, in basis points. Deducted from the insurer's settlement proceeds. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Commission config UUID. |
| scope | string | global | insurance_company | broker. |
| insurance_company_id / broker_id | UUID | null | Whichever applies to this row's scope. |
| gtbank_rate_bps / insureflow_rate_bps / broker_rate_bps | integer | null | The three rates. |
| effective_from | datetime | When this rate became active. |
| effective_to | datetime | null | When superseded. null means currently active. |
| created_by | UUID | The 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.
/commission-configsList commission configurations
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.
/remindersSend a reminder
Emails the premium's broker a reminder to pay it.
Request body
| Field | Type | Description |
|---|---|---|
| installment_id* | UUID | Must be due or overdue. |
Response
| Field | Type | Description |
|---|---|---|
| id | UUID | Reminder UUID. |
| installment_id / broker_id / sent_by | UUID | What was reminded, and by whom. |
| channel | string | Always "email". |
| status | string | queued, then sent once the email goes, or failed if it can't be sent after an hour of retries. |
| sent_at | datetime | null | When the email was sent. |
| created_at | datetime | ISO 8601 UTC. |
/reminders/bulkRemind brokers about every overdue premium
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
| Field | Type | Description |
|---|---|---|
| reminders_created | integer | Premiums reminded about. |
| brokers_emailed | integer | Brokers emailed, one email each. |
/remindersList reminders
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.
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.
/settlementsList settlement payouts
Query parameters
| Field | Type | Description |
|---|---|---|
| status | string | queued | pending | success | failed. |
Response (array item fields)
| Field | Type | Description |
|---|---|---|
| id | UUID | Settlement payout UUID. |
| insurance_company_id | UUID | The receiving insurer. |
| source_type | string | "remittance_line" (one premium); "remittance", "payment" or "payment_batch" for older payouts. |
| source_id | UUID | The remittance line (or older remittance / Payment / PaymentBatch) it pays out. |
| remittance_id | UUID | null | The remittance the premium was paid in. |
| narration | string | null | The transfer remark, e.g. "ADEBAYO OLUWASEUN POL P-0012 DN DN-55012 IFW-7K3QX". |
| amount_kobo | integer | Amount transferred to the insurer. |
| fee_kobo | integer | Payout fee deducted before the transfer. |
| status | string | queued (not yet sent) | pending (sent, awaiting confirmation) | success | failed. |
| squad_transfer_ref | string | Server-generated Squad transfer reference. |
| attempt_number | integer | 1 unless a prior attempt failed and was retried. |
| previous_attempt_id | UUID | null | Chains to the attempt this one retried, if any. |
| failure_reason | string | null | Populated only if status is failed. |
| created_at | datetime | ISO 8601 UTC. |
/settlements/{payout_id}Get a settlement payout
Path parameters
| Field | Type | Description |
|---|---|---|
| payout_id* | UUID | The 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.
/webhook-endpointsAdd a webhook endpoint
Registers an endpoint and returns its signing secret — the only time the secret is returned. Up to 5 endpoints per organization.
Request body
| Field | Type | Description |
|---|---|---|
| url* | string | An https:// URL on a public host, at most 2048 characters. |
| description | string | null | A label for your own reference. |
| events | string[] | null | Any 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
| Field | Type | Description |
|---|---|---|
| endpoint | WebhookEndpoint | id, url, description, events, is_active, created_at, updated_at. |
| secret | string | The whsec_… signing secret. Store it now; it can't be retrieved again. |
/webhook-endpointsList webhook endpoints
Your organization's endpoints. Never includes secrets.
Array of WebhookEndpoint objects.
/webhook-endpoints/{endpoint_id}Get a webhook endpoint
Path parameters
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
A WebhookEndpoint object.
/webhook-endpoints/{endpoint_id}Update a webhook endpoint
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
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
Request body
| Field | Type | Description |
|---|---|---|
| url | string | New https:// URL. |
| description | string | New label. |
| events | string[] | New subscription — at least one event. |
| is_active | boolean | Enable or disable the endpoint. |
The updated WebhookEndpoint.
/webhook-endpoints/{endpoint_id}Delete a webhook endpoint
Stops all deliveries to the endpoint and deletes its delivery history.
Path parameters
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
HTTP 204 No Content.
/webhook-endpoints/{endpoint_id}/rotate-secretRotate the signing secret
Issues a new secret. The old one stops signing immediately, including for retries already queued.
Path parameters
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
Response
| Field | Type | Description |
|---|---|---|
| endpoint | WebhookEndpoint | The endpoint. |
| secret | string | The new whsec_… secret, shown only once. |
/webhook-endpoints/{endpoint_id}/testSend a test event
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
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
HTTP 202 with the queued WebhookDelivery.
/webhook-endpoints/{endpoint_id}/deliveriesList recent deliveries
The 50 most recent deliveries to this endpoint, newest first.
Path parameters
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
Query parameters
| Field | Type | Description |
|---|---|---|
| status | string | pending | succeeded | failed. |
Response (array item fields)
| Field | Type | Description |
|---|---|---|
| id | UUID | Delivery UUID — use it to redeliver. |
| event_id | UUID | The event's id, as sent in Insureflow-Event-Id. |
| event_type | string | e.g. remittance.paid. |
| status | string | pending | succeeded | failed. |
| attempt_count | integer | Attempts made so far. |
| next_attempt_at | datetime | null | When the next retry is due, while pending. |
| last_response_status | integer | null | Your endpoint's last HTTP status, if it responded. |
| last_error | string | null | Why the last attempt failed. |
| delivered_at | datetime | null | When a 2xx was received. |
| payload | object | The exact event body sent. |
/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/redeliverRedeliver an event
Sends the same event — same id, same body — again, with a fresh retry schedule.
Path parameters
| Field | Type | Description |
|---|---|---|
| endpoint_id* | UUID | The endpoint's UUID. |
| delivery_id* | UUID | The 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.
| Event | Sent when |
|---|---|
| remittance.paid | A 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.expired | An open remittance was never paid and its premiums were released. Broker only. |
| receipt.awaiting_broker | Money 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.refunded | Unmatched money was sent back to the broker. Broker, and the insurer it was meant for if the broker said. |
| premium.settled | One 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_failed | One premium's transfer failed after every automatic retry and needs Insureflow to retry it. Insurer and broker. |
| webhook.test | You clicked Send test event, or called POST /webhook-endpoints/{id}/test. |
Payload
Every event has the same envelope:
{
"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
| Header | Value |
|---|---|
| Content-Type | application/json |
| Insureflow-Event-Id | The event's id. The same across retries — de-duplicate on it. |
| Insureflow-Event-Type | The event's type. |
| Insureflow-Signature | t=<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:
- Use the raw request body, exactly as received. Re-serializing parsed JSON changes the bytes.
- Reject requests whose
tis more than 5 minutes old, to block replays. - Compare signatures in constant time.
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)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-Idyou'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}orGET /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
- Insurer and broker both complete onboarding (
POST /insurance-companies,POST /brokers) and are approved by Insureflow; both activate with their one-time token. - The insurer adds the brokers it works with (
GET /insurance-companies/me/broker-directory, thenPOST /insurance-companies/me/brokers). - The insurer sets its settlement account (
POST /insurance-companies/{company_id}/settlement-account). - The insurer creates the policy, naming the servicing broker, with its client's details in
policyholder— or an existing client'spolicyholder_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
- List due or overdue installments for the policyholder (
GET /installments?status=due). - Create a remittance for one premium or many (
POST /remittances) with a freshIdempotency-Key. All its premiums must be for one insurer. - Transfer exactly
pay_to.amount_kobointopay_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). - Wait for a
remittance.paidwebhook, or pollGET /remittances/{id}until its status leavesopen. Oncepaid, every premium is marked paid and the insurer is paid at once — one transfer per premium, each announced by apremium.settledwebhook. - 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
- 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. - 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. - As the insurer, add the broker under Brokers, then create a policy for it.
- As the broker, open Policies, select the due premiums and press Pay. On the remittance page, press Simulate this transfer.
- 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)
- Optional: register a webhook endpoint (
POST /webhook-endpoints) and store the secret it returns. - Create a policy for the test broker with its client's details (
POST /policieswithpolicyholder: { full_name, email }), e.g.premium_amount_kobo: 10000000for a ₦100,000 premium withduration_months: 12— or upload many at once withPOST /policies/import. - List its premiums (
GET /policies/{id}/installments) and note theirids.
2. Pay it (broker key)
- Create a remittance for one or more of those premiums (
POST /remittanceswith anIdempotency-Key). The response shows each premium's gross, broker commission and net, the exact total to send, and the reference. - Simulate the transfer (
POST /remittances/{id}/simulate-transfer). It comes backpaid.
3. See what the insurer receives (insurer key)
GET /remittances/{id}: each line now shows the bank's collection fee, Insureflow's 0.5% and the insurer's net.GET /remittances/{id}/advice.xlsx: the remittance advice, one row per debit note, to reconcile against your books.GET /settlements: one payout per premium to your settlement account, each narrated with the client's name, policy number and debit note.GET /policies/{id}/installments: those premiums are nowpaid.- Your endpoint receives
remittance.paid, then onepremium.settledper premium (seeGET /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 inGET /receiptsawaiting 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}/cancelreleases the premiums. An unpaid remittance also expires after 14 days.
Sandbox vs live
| Step | Sandbox vs live |
|---|---|
| Broker's transfer | Sandbox: 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 insurer | One 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-up | Sandbox: insurers and brokers are approved as soon as they sign up. Live: Insureflow reviews and approves each one. |
| Settlement account | Sandbox: 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 keys | Separate. 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.
| Code | Bank |
|---|---|
| 000014 | Access Bank |
| 000009 | Citibank |
| 060001 | Coronation Merchant Bank |
| 000010 | Ecobank |
| 060002 | FBNQuest Merchant Bank |
| 000003 | FCMB |
| 000007 | Fidelity Bank |
| 000016 | First Bank of Nigeria |
| 000027 | Globus Bank |
| 060004 | Greenwich Merchant Bank |
| 000013 | GTBank |
| 000006 | Jaiz Bank |
| 000002 | Keystone Bank |
| 000029 | Lotus Bank |
| 060003 | Nova Merchant Bank |
| 000036 | Optimus Bank |
| 000008 | Polaris Bank |
| 000031 | PremiumTrust Bank |
| 000023 | Providus Bank |
| 000024 | Rand Merchant Bank |
| 000034 | Signature Bank |
| 000012 | Stanbic IBTC Bank |
| 000021 | Standard Chartered |
| 000001 | Sterling Bank |
| 000022 | SunTrust Bank |
| 000026 | Taj Bank |
| 000025 | Titan Trust Bank |
| 000004 | UBA (United Bank for Africa) |
| 000018 | Union Bank |
| 000011 | Unity Bank |
| 000017 | Wema Bank |
| 000015 | Zenith Bank |