Payment links
A page we host, at a URL you send to whoever is paying. No code, no embedding, no checkout page of your own.
A link is not a factory that mints a session per visitor. It owns an address and a running total, which is why it has no countdown: a link closes on a threshold or a date, not on a per-visitor timer.
Two modes
fixed — one amount, one payer
A preconfigured amount and a unique address generated for the page. On payment the page becomes unavailable.
curl -X POST $CHAINOS/v1/payments/links \
-H "X-API-Key: $CHAINOS_PAYMENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"mode": "fixed",
"chain": "base",
"asset": "USDC",
"amount": "1250.00",
"title": "Invoice 4418",
"description": "Consulting, August",
"collectEmail": true
}'
The URL is generated and never chosen. A guessable slug on an invoice publishes what one
customer was charged, so fixed links always get a random one.
donation — many payers, accumulating
Arbitrary amounts from many payers, accumulating until a threshold is reached or a closing date passes, after which the page becomes unavailable.
curl -X POST $CHAINOS/v1/payments/links \
-H "X-API-Key: $CHAINOS_PAYMENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"mode": "donation",
"addressMode": "per_payer",
"chain": "polygon",
"asset": "USDT",
"thresholdAmount": "50000",
"closesAt": "2026-12-31T23:59:59Z",
"minAmount": "1",
"slug": "harvest-appeal",
"title": "Harvest appeal",
"collectEmail": true
}'
A donation link must have a threshold, a closing date, or both. One with neither is refused: an open-ended link is an address nobody ever stops watching.
Custom slugs are honoured on donation links only, for the same reason fixed links do not get them.
addressMode decides whether donors get their names on receipts
This is the decision worth making deliberately.
per_payer (default) | shared | |
|---|---|---|
| Address | One derived per donor | One for the life of the link |
| The donor | Enters an email, gets their own address | Sends to the address on the page |
| Receipts | Attributable, with a name | Anonymous unless volunteered |
| Concurrent donors | Told apart exactly | Best effort |
| Printable, postable | No | Yes — that is the point |
| Invoices | Issued | None |
A shared address is what you want on a poster, a printed QR or a page you link from social media. The cost is real and stated: two donors paying at the same moment cannot be told apart well enough to put a name on a receipt, so no invoice is issued at all. It is the same shared-address-plus-memo model ChainOS deliberately rejected for XRP and Stellar, and it matters less here only because nothing ships.
Every payment still accumulates toward the link's total in both modes.
fixed links are always effectively per_payer with a single payer.
What comes back
{
"id": "0b1f…",
"slug": "harvest-appeal",
"url": "https://pay.your-company.example/pay/harvest-appeal",
"mode": "donation",
"addressMode": "per_payer",
"amountRaised": "0",
"paymentCount": 0,
"thresholdAmount": "50000",
"closesAt": "2026-12-31T23:59:59Z",
"address": null,
"status": "active"
}
address is present only in shared mode, where the link itself holds it.
amountRaised is confirmed money only. A reorg can still undo a detected payment, and
a total that counted unconfirmed deposits would go backwards on a public page.
Closing
A link closes by itself when its condition is met, and closedReason says which:
closedReason | Meaning |
|---|---|
paid | A fixed link took its payment |
threshold | A donation link reached its target |
date | The closing date passed |
manual | Somebody closed it |
Closing is irreversible; the total is frozen at what it raised. To take a page down temporarily, pause it instead — the total is kept and resuming puts it back.
curl -X POST $CHAINOS/v1/payments/links/$ID/pause -H "X-API-Key: $KEY"
curl -X POST $CHAINOS/v1/payments/links/$ID/resume -H "X-API-Key: $KEY"
curl -X POST $CHAINOS/v1/payments/links/$ID/close -H "X-API-Key: $KEY"
A closed page does not mean money sent to it is lost. The address keeps being monitored
until monitorUntil and late funds are credited, exactly as they are for a checkout
session — see Late payments.
Webhooks
Payments through a link fire the same payment_* events as any other, with linkId set
and createdVia: "link". payment_link_created and payment_link_closed cover the link
itself.
For a donation campaign, payment_link_closed carries amountRaised and paymentCount —
the two numbers you want for the announcement.
Editing
PATCH /v1/payments/links/{id} changes presentation, bounds and closing conditions:
title, description, successUrl, collectEmail, minAmount, maxAmount,
thresholdAmount, closesAt, maxUses, metadata.
It does not change the chain, the asset, the mode or a fixed link's amount. Those are what the address was derived for and what donors have already paid against; changing them would rewrite the meaning of money already received.
In the portal
Payment links in the sidebar. Mode, raised-against-target, what will close it and when, and copy-URL, pause and close on each row. Closing is behind a confirmation because it cannot be undone.