Skip to main content
Each flow below is complete: the happy path, and the failures worth handling.

1. Onboard a customer and give them a wallet

The most common flow. Create the customer, then open an account naming them.
Passing customerId is what makes the account a wallet belonging to that customer. Omit it and the account belongs to your business. The branch that matters. If the customer was created but the account call failed, you have a customer with no wallet — and retrying the whole flow hits 409 customer_already_exists. Make the create idempotent from your side:
That last branch is worth keeping. 409 customer_already_exists covers externalReference and every identity number, so “already exists” does not always mean “the one I am looking for”.

2. Keep your records and Sakawave in step

Your system is the source of truth for customer details. When something changes there, push it.
Three rules govern PATCH:
  • Absent leaves alone; null clears. {"middleName": null} erases a middle name. Omitting middleName keeps it.
  • address replaces wholesale. It is not merged field by field. Send the complete address, or you will silently clear line2, state, and postalCode.
  • Type-specific fields are refused, not ignored. Sending name for an individual returns 400 customer_fields_do_not_match_type with the offending names in fields. This catches the common bug where one code path patches every customer with the same payload shape.
If you drive updates from your own change events, guard against a loop: a PATCH emits customer.updated, and a receiver that writes back on every event will chase its own tail.

3. Find customers

Reads are filtered with field=value, or field:operator=value where the field supports more than equality.
A repeated parameter and a comma-separated one mean the same thing: jurisdiction:in=NG&jurisdiction:in=GH equals jurisdiction:in=NG,GH. Unknown filters are rejected by name rather than ignored — ?statuss=active fails with 400 and tells you which parameter it did not recognise. This is deliberate: a silently ignored filter returns a wider result set than you asked for, which is how the wrong customers end up in a report. Paging a full export. Page size defaults to 25 and caps at 100; a larger value is rejected rather than quietly capped, so your arithmetic stays right. Results are ordered newest first with id as a tiebreaker, so pages are stable unless a customer is created mid-scan — pin createdAt:lte at the start of the job if that matters.

4. Offboard a customer

Archiving is refused while the customer still holds anything live.
A 204 means archived. A 409 customer_has_active_products means there is still an open wallet account or a live card:
The order is therefore: close every account, terminate every card, then archive.
Cards can be terminated through this API. Accounts cannot be closed through it — closing is a dashboard action. An offboarding flow built entirely on the Business API therefore stops one step short of archiving, and needs a person to finish it. See the accounts overview.
Two consequences to plan for:
  • The reference is released. After archiving, externalReference and the identity numbers become reusable by a new customer. If your own records rely on that reference being unique forever, keep your own history — Sakawave will not stop it being reissued.
  • There is no unarchive. Recreating produces a new id. Anything you stored against the old one still points at a customer that reads answer 404 for.
Archiving emits no webhook. A local mirror should detect archives by reconciling against GET /v1/customers, treating a record that has disappeared as archived.