Open a collection
Mint a reference and USD deposit instructions for one collection, read the fee terms up front, then read the collection back at any time.
Collections are a separate product from the sell/buy USDT rails covered
elsewhere in these guides — see
Products and scopes for which credential
you need. A collection is one thing: a reference plus a set of USD
deposit instructions, opened for one deposit. The first deposit
attributed to it moves it out of open, and nothing after that is added to
it — open a new collection for every payment you expect, even from the same
contributor.
sequenceDiagram
autonumber
accTitle: Opening a collection and reading it back
accDescr: You create a collection, naming its currency and your own reference. OnLink mints a reference and sets up its USD deposit instructions, returning both plus the fee terms. You hand the deposit instructions to your contributor. Later you read the collection by id, or list your collections filtered by reference, status and date.
participant You
participant OnLink
participant Contributor
You->>OnLink: POST /v1/collections (currency, partnerReference, payerLabel)
OnLink->>OnLink: mint a reference, set up deposit instructions
OnLink-->>You: 201 id, reference, depositInstructions, fee
You->>Contributor: hand over the deposit instructions
You->>OnLink: GET /v1/collections/:id (any time)
OnLink-->>You: the collection, refreshed1. Create the collection
POST /v1/collections takes the currency, your own reference for the
collection, and a label for your own records:
| Field | Notes |
|---|---|
currency | Only USD is supported today. |
partnerReference | Required, 1–128 characters. Your own handle — a community id, a campaign name. Stored for your audit trail only. |
payerLabel | Required, 1–200 characters. A plain-text label for your own records (e.g. "September community appeal"). Never shown to the contributor. |
expectedAmount | Optional, advisory only. Decimal string, up to 13 digits before the point and 2 after. A mismatch against what actually arrives is never refused. |
partnerReference and payerLabel come back as partnerReference and label
A create response, a read and a list return what you sent: partnerReference
as partnerReference and payerLabel as label. Both are null on a
collection opened before we stored them. GET /v1/collections?partnerReference= matches your own partnerReference
exactly, and GET /v1/collections?reference= matches the reference we
mint (below). The two are different filters; use whichever handle you hold.
Without an Idempotency-Key, a create is not deduplicated
Two calls with the same partnerReference open two separate collections —
nothing dedupes on it. And a 503 does not guarantee nothing happened: the
collection row can already be saved before a downstream failure. Send an
Idempotency-Key (next section) so a retry is safe, or list your collections
(GET /v1/collections?partnerReference=) before retrying rather than assuming
the first call did nothing.
Idempotent create
Send an optional Idempotency-Key header on POST /v1/collections. It is 8 to
64 characters from A-Z, a-z, 0-9, _ and -; anything else, including an
empty header, is refused 400 INVALID_IDEMPOTENCY_KEY and nothing is created.
Use a value you generate once per intended collection (a UUID, or your own
order id) and send it unchanged on every retry.
- A retry with the same key and the same body returns the same collection,
with the same
idandreference. No second collection and no second set of deposit instructions is created, so a timeout or a dropped connection can be retried safely. - The same key with a different body is refused
422 IDEMPOTENCY_KEY_REUSED. The body is compared oncurrency,partnerReference,payerLabelandexpectedAmount. Use a new key for a new collection. - The comparison is made against the collection as it stands now. If the
collection was edited after you created it, a retry of the original create no
longer matches and is refused
IDEMPOTENCY_KEY_REUSED, even though you sent exactly what you sent the first time. Read the collection (GET /v1/collections?partnerReference=) instead of retrying. - A retry can be refused for the same reasons as the first call. If we
still cannot open the deposit account, the retry returns that
503(or the409that says a setup is already in progress), never a201withoutdepositInstructions. - A key belongs to your account: another partner sending the same string does not collide with you. Sending no key keeps the behaviour above: every call opens a new collection.
Response — 201 Created:
{
"id": "a1b2c3d4-0000-4000-8000-000000000001",
"reference": "DEMO-7K3Q2M",
"status": "open",
"currency": "USD",
"partnerReference": "campaign-2026-09",
"label": "September community appeal",
"depositInstructions": {
"beneficiaryName": "<beneficiary name>",
"bankName": "<bank name>",
"routingNumber": "000000000",
"accountNumber": "00000000123456",
"rails": ["ach_push", "fednow", "wire"],
"depositMessage": "SBX0DEPOSITMESSAGE"
},
"fee": {
"percentBps": 100,
"flatMinor": 50,
"capMinor": null,
"currency": "USD"
},
"createdAt": "2026-09-22T17:38:46.915Z"
}reference is minted for you — an unambiguous code (no 0, 1, I, L or
O, which are the characters people misread off a screen or a bank
statement). depositInstructions is a complete set of US deposit
details — a beneficiary name, a bank, a routing number and an account number.
We never compose any of these fields ourselves; what you see here is exactly
what your contributor should be given, unedited.
Hand over the deposit instructions exactly as given
Do not retype, reformat or "clean up" any field in depositInstructions —
copy it exactly. A beneficiary name, account number or depositMessage
decides whether the money lands or is attributed to this collection at all.
depositMessage — quote it, or expect delays
Most collections share ONE deposit account across many references, each
distinguished by depositMessage — a code we mint (never chosen by you or
your contributor). Your contributor must include it in their transfer (a
wire memo, an ACH description) for the deposit to be attributed to this
collection. A deposit that arrives without it matches no collection, so it is
not credited and does not appear on this collection at any status — it has to
be reconciled by hand, or returned to your contributor.
A small number of collections instead get a dedicated account number with
no depositMessage at all — in that case the field is simply absent, and any
deposit into that account is already unambiguous. Branch on whether the field
is present; never assume one shape or the other.
2. Read the fee terms up front
fee on the create response is your own pricing terms — a percentage, a flat
amount, and an optional cap — shown before any money has moved. capMinor: null means uncapped. These are TERMS, not a computed amount: nothing has been
deposited yet, so there is nothing to compute a real fee against yet. Once a
deposit is attributed to the collection, the fee is applied to it and the read
below carries the result as grossAmount, feeAmount and netAmount.
3. Read a collection back at any time
GET /v1/collections/:id returns the same shape, refreshed:
{
"id": "a1b2c3d4-0000-4000-8000-000000000001",
"reference": "DEMO-7K3Q2M",
"status": "open",
"currency": "USD",
"partnerReference": "campaign-2026-09",
"label": "September community appeal",
"depositInstructions": {
"beneficiaryName": "<beneficiary name>",
"bankName": "<bank name>",
"routingNumber": "000000000",
"accountNumber": "00000000123456",
"rails": ["ach_push", "fednow", "wire"],
"depositMessage": "SBX0DEPOSITMESSAGE"
},
"fee": {
"percentBps": 100,
"flatMinor": 50,
"capMinor": null,
"currency": "USD"
},
"createdAt": "2026-09-22T17:38:46.915Z"
}depositInstructions is returned only while the collection is open; once a
deposit has been attributed it is never present again. On an open collection
we refresh it best-effort every time you read it, and a failed refresh omits
the field rather than inventing one — there, treat its absence as "check again
shortly", not an error.
GET /v1/collections?reference=&partnerReference=&status=&from=&to= lists your
own collections, newest first, no paging, filtered by any combination of a
status and a creation-time window, plus either handle:
referencematches the reference we minted and returned to you (e.g.DEMO-7K3Q2M).partnerReferencematches your own reference, exactly and case-sensitively (1–128 characters; an empty value is a400). It can match more than one collection, becausepartnerReferenceis not unique: opening two collections with the same value is allowed, so a list is the right shape for the answer.
Cancelled collections are listed like any other; filter on status to include
or exclude them. Every filter is optional and every result is scoped to your own
partner account — a collection you did not open never appears, whatever you
search for.
What happens when a deposit arrives
Opening a collection and reading it back — everything above — is live, and so
is what happens when a deposit arrives with the right depositMessage (or on a
dedicated account number):
- The first deposit is attributed to this collection, your fee is applied, and
the collection moves to
received, withgrossAmount,feeAmountandnetAmounton every read from then on. - We pay the net amount (gross less fee) into your USD account. While that
payout is in flight the collection reads
crediting. - When the payout settles the collection becomes
credited, and we send you thecollection.receivedwebhook.
A later deposit sent with the same instructions is not added to this
collection. Like a deposit without its depositMessage, it has to be
reconciled by hand or returned.
credited is the point at which the money is in your account — not the moment
the deposit arrived. If the payout did not go through, the collection returns
to received and the payout is retried; you do not need to do anything. A
collection that stays in crediting longer than you expect means
we are still confirming the outcome of a payout — contact us with the id
rather than assuming either result.
The response schema documents the full lifecycle, so handle every value:
status | Meaning |
|---|---|
open | The deposit instructions are live and no deposit has arrived. |
received | A deposit has arrived and been matched to this collection; your fee is applied. |
crediting | The deposit has been received and the net amount is being paid into your account; it becomes credited when the payout settles, or returns to received if the payout did not go through, and is then retried. |
credited | The net amount has been paid into your account. collection.received is sent at this point. |
refunded | The deposit was returned to the sender. A collection can move here from credited as well as from received, and no webhook is sent when it does — read the collection back before treating a credited amount as final, and contact us about any credited collection that reads refunded. |
expired | We closed the collection before any deposit arrived. Do not hand its deposit instructions out again: a deposit sent to them afterwards is not attributed to this collection. |
cancelled | The business cancelled it before a payment arrived. Do not hand its deposit instructions out again; a payment sent to it is not credited to you — contact support. |
A collection never shows you a token, a chain, or an exchange rate.
What you can do with the USD
- Read it —
GET /v1/balancescarries your USD account underusd, beside your KES account underkes. A credited collection raisesusd.available. - Convert it to KES — see Convert USD to KES, and Collect in the US, pay out in Kenya for the whole journey through to a payout in Kenya.
- Pay it out as USD (RTGS, EFT or SWIFT), to a USD account — see Paying out USD.
Testing in sandbox
Nobody can send real money to a sandbox collection, so
POST /v1/simulations/collections/{id}/deposits
makes a payer pay it. The collection is attributed and moves to received, with
grossAmount, feeAmount and netAmount, as in the list above. While sandbox
crediting is enabled it is then credited to your simulated USD balance and
moves to credited, and collection.received is sent, as in production;
otherwise it stays received. While crediting is enabled, only a collection
created while sandbox money is simulated can be simulated: an older one answers
503 SIMULATION_NOT_CONFIGURED, so create a new one. Each call is a new payer. See
Testing in sandbox for the request, the 200
and 202 answers and the X-OnLink-Money-Mode header that marks a simulated
collection on your reads.
Errors worth handling
Every code on this route is catalogued in Errors — Collections. The ones worth knowing before you build:
| Status | Meaning |
|---|---|
503 | Collections are not yet enabled for your account, or we could not open the deposit account. |
404 | No collection with that id belongs to you. |
400 | INVALID_IDEMPOTENCY_KEY: the Idempotency-Key header is not 8 to 64 characters of A-Z, a-z, 0-9, _ and -. Nothing was created. |
422 | IDEMPOTENCY_KEY_REUSED: that key was used with different details, or the collection was edited since. Read the collection; use a new key for a new create. |
Send USDT from your wallet
Create an instruction, your registered person confirms it with a one-time code, then it executes. One rail, one consent step, one honest failure model.
Convert USD to KES
Turn the USD in your USD account into KES in your KES account with one call, and know exactly what rate you were paid.