OnLink
Guides

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, refreshed

1. Create the collection

POST /v1/collections takes the currency, your own reference for the collection, and a label for your own records:

FieldNotes
currencyOnly USD is supported today.
partnerReferenceRequired, 1–128 characters. Your own handle — a community id, a campaign name. Stored for your audit trail only.
payerLabelRequired, 1–200 characters. A plain-text label for your own records (e.g. "September community appeal"). Never shown to the contributor.
expectedAmountOptional, 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 id and reference. 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 on currency, partnerReference, payerLabel and expectedAmount. 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 the 409 that says a setup is already in progress), never a 201 without depositInstructions.
  • 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:

  • reference matches the reference we minted and returned to you (e.g. DEMO-7K3Q2M).
  • partnerReference matches your own reference, exactly and case-sensitively (1–128 characters; an empty value is a 400). It can match more than one collection, because partnerReference is 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):

  1. The first deposit is attributed to this collection, your fee is applied, and the collection moves to received, with grossAmount, feeAmount and netAmount on every read from then on.
  2. We pay the net amount (gross less fee) into your USD account. While that payout is in flight the collection reads crediting.
  3. When the payout settles the collection becomes credited, and we send you the collection.received webhook.

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:

statusMeaning
openThe deposit instructions are live and no deposit has arrived.
receivedA deposit has arrived and been matched to this collection; your fee is applied.
creditingThe 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.
creditedThe net amount has been paid into your account. collection.received is sent at this point.
refundedThe 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.
expiredWe 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.
cancelledThe 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

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:

StatusMeaning
503Collections are not yet enabled for your account, or we could not open the deposit account.
404No collection with that id belongs to you.
400INVALID_IDEMPOTENCY_KEY: the Idempotency-Key header is not 8 to 64 characters of A-Z, a-z, 0-9, _ and -. Nothing was created.
422IDEMPOTENCY_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.

On this page