Payments

Invoices

Create, list, retrieve and void invoices; every field; quotes; and how the hosted checkout handles network selection, live status and the return to your site.

Last updated Oct 27, 2026

An invoice asks a buyer for a USD amount, paid in USDT or USDC on one network. Each invoice gets its own deposit address, so a payment is matched to its invoice by the address it was sent to.

Create an invoice

POST /api/v1/crypto/invoices — permission crypto-invoices:create, header Idempotency-Key required (see Idempotency).

There are two ways to create one:

  • Fixed network and token. Send network and token. The invoice is PENDING immediately and the response carries deposit_address, total and payment_uri.
  • Buyer chooses. Omit both. The invoice takes the environment of your API key; with a legacy key, send mode to choose it: Sandbox (mode: "test" in the API) or Live ("live"). The invoice is AWAITING_SELECTION; the buyer picks a network and token on the hosted checkout, which assigns the address then.

The smallest valid requests:

{ "amount": "49.00", "title": "Pro plan, 1 month", "network": "POLYGON_AMOY", "token": "USDC" }
{ "amount": "49.00", "title": "Pro plan, 1 month", "mode": "test" }

A complete request, with a seller, a partner share and a markup:

curl -X POST https://api.aliquot.cc/api/v1/crypto/invoices \
  -H "Authorization: Bearer $ALIQUOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-77310" \
  -d '{
    "amount": "100.00",
    "network": "POLYGON_AMOY",
    "token": "USDC",
    "fee_payer": "CUSTOMER",
    "markup_percent": 2,
    "merchant": "halden_type",
    "splits": [{ "account": "kerning_weekly", "percent": 10 }],
    "title": "Halden Grotesk — Desktop license",
    "merchant_order_id": "order-77310",
    "buyer_email": "[email protected]",
    "redirect_url": "https://shop.example.com/orders/77310",
    "metadata": { "cart_id": "c_5521" }
  }'

Request fields

FieldTypeRequiredDescription
amountstringYesPrice in USD, at most two decimals. Minimum "1.00".
titlestringYesWhat is being paid for, 1–200 characters. Shown on the checkout.
currencystringNoOnly "USD", the default.
networkstringWith tokenOne of POLYGON_MAINNET, POLYGON_AMOY, BSC_MAINNET, BSC_TESTNET, BASE_MAINNET, BASE_SEPOLIA. Send it together with token, or omit both.
tokenstringWith networkUSDT or USDC, available on that network (see Networks and tokens).
modestringWith a legacy key, when no network"test" (Sandbox) or "live". If you also send network, they must agree. A Sandbox or Live key fills in its own mode and answers 403 API_KEY_ENVIRONMENT_MISMATCH for the other one.
fee_payerstringNoCUSTOMER (default): the buyer pays the processing fee and the markup on top of amount. MERCHANT: the buyer pays amount and both come out of the seller's share.
markup_percentnumberNoYour platform's cut, 0–20, at most two decimals. Default 0.
merchantstringNoReference of the MERCHANT account selling. Omitted: your platform account is the seller. A PARTNER account cannot be the merchant.
splitsarrayNoUp to 20 { "account": "<reference>", "percent": <number> } entries. Each percent is above 0 with at most two decimals; together at most 100. No account twice.
descriptionstringNoUp to 2000 characters.
merchant_order_idstringNoYour order ID, 1–200 characters. Searchable with q and included in the invoice.created and refund.claimable webhooks.
redirect_urlstringNoWhere the checkout sends the buyer after paying. See Returning to your site.
metadataobjectNoAny JSON object. Stored and returned as is.
buyer_emailstringNoIf set, the buyer receives refund claim links by email. Never shown on the checkout.
expires_in_minutesintegerNoPayment window, 1–1440. Default 30. Counted from creation when you send network and token, otherwise from the moment the buyer picks a pair. See Payment window.

How fee_payer, markup_percent and splits turn into amounts is explained, with a worked example, in Ledger and splits. Accounts named in merchant and splits must exist; an unknown reference is a 422 on that field.

The invoice object

201 Created returns the invoice. A replay of the same Idempotency-Key returns it with 200.

FieldDescription
idinv_….
statusSee Statuses.
livemodefalse in Sandbox, true in Live.
currency"USD".
fee_payerCUSTOMER or MERCHANT.
network, token, decimalsThe payment pair and the token's decimals. null while AWAITING_SELECTION.
baseThe price in token units once a pair is selected; the USD price before.
platform_fee, markup, merchant_net, totalThe fee breakdown in token units. total is what the buyer must send. null while AWAITING_SELECTION.
receivedSum of the transfers in the invoice's token seen so far.
deposit_addressThe address the buyer pays to. null while AWAITING_SELECTION.
payment_uriAn EIP-681 URI for wallets and QR codes, requesting exactly total of the token.
required_confirmationsThe confirmation threshold that applies to this invoice. See Confirmations.
expires_atEnd of the payment window.
merchant{ "reference", "name" } of the seller.
splits[{ "account", "name", "percent", "amount" }]; amount is null until a pair is selected.
title, description, merchant_order_id, redirect_url, metadata, buyer_emailAs sent.
payment_link_idThe payment link that created the invoice (plink_…), or null for an invoice created with this endpoint.
pay_urlThe hosted checkout: https://aliquot.cc/pay/{id}.
created_at, detected_at, paid_at, cancelled_atTimestamps; null until they happen.

Statuses

StatusMeaningWebhook on entry
AWAITING_SELECTIONWaiting for the buyer to pick a network and token.invoice.created (at creation)
PENDINGAddress assigned, waiting for a transfer.invoice.created (at creation)
DETECTEDA transfer was seen and is confirming. Does not expire.invoice.detected
PAIDConfirmed, and the amount covers the total.invoice.paid
OVERPAIDConfirmed with more than the total; the excess is refundable.invoice.paid and invoice.overpaid
UNDERPAIDConfirmed but short. The buyer can send the rest before expires_at.invoice.underpaid
EXPIREDThe window ended with nothing received.invoice.expired
EXPIRED_UNDERPAIDThe window ended with a partial payment, held for the buyer as a refund.invoice.expired (and refund.claimable)
REFUNDEDThe partial payment was returned to the buyer.refund.completed
CANCELLEDYou voided it.None

The full lifecycle diagram is in the Overview.

Retrieve an invoice

GET /api/v1/crypto/invoices/{id} — permission crypto-invoices:read.

Returns the invoice object plus:

FieldDescription
idempotency_keyThe Idempotency-Key it was created with (null for invoices started from a payment link).
transfers[{ "tx_hash", "token", "amount", "confirmations", "required", "status", "block_number" }]. status is SEEN (confirming), CONFIRMED or ORPHANED (its block was replaced by a reorg). token differs from the invoice's for a wrong-token payment.
ledgerOnce PAID or OVERPAID: the settlement posting, { "id", "entries": [{ "side", "account", "amount" }] }. See Ledger and splits.
refundsRefunds held for the buyer of this invoice, with claim_url while claimable. See Refunds.

List invoices

GET /api/v1/crypto/invoices — permission crypto-invoices:read.

Query parameterDescription
modetest or live. Defaults to your key's environment (live with a legacy key).
statusOne status from the table above.
qSearch by invoice ID, deposit address, transaction hash or merchant_order_id.
cursor, limitPagination; limit 1–100, default 25.
curl "https://api.aliquot.cc/api/v1/crypto/invoices?mode=test&q=order-77310" \
  -H "Authorization: Bearer $ALIQUOT_API_KEY"

The response is { "items": [...], "next_cursor": "..." }, newest first.

Void an invoice

POST /api/v1/crypto/invoices/{id}/void — permission crypto-invoices:update. No body.

Voiding moves an AWAITING_SELECTION or PENDING invoice to CANCELLED and returns it. Any other status answers 409 CONFLICT: once a payment is detected, the invoice can no longer be voided. Voiding sends no webhook.

If the buyer pays a cancelled invoice anyway, the money is not credited to the seller; it is held for the buyer as a LATE_PAYMENT refund.

Quote

POST /api/v1/crypto/quote — permission crypto-invoices:read. Computes the same breakdown an invoice would get, without creating anything.

{
  "amount": "100.00",
  "network": "POLYGON_AMOY",
  "token": "USDC",
  "fee_payer": "CUSTOMER",
  "markup_percent": 2,
  "merchant": "halden_type",
  "splits": [{ "account": "kerning_weekly", "percent": 10 }]
}

network and token are required; fee_payer, markup_percent, merchant and splits are optional and default as on an invoice. The response:

{
  "network": "POLYGON_AMOY",
  "token": "USDC",
  "decimals": 6,
  "fee_payer": "CUSTOMER",
  "base": "100.00",
  "platform_fee": "0.50",
  "markup": "2.00",
  "merchant_net": "90.00",
  "total": "102.50",
  "splits": [{ "account": "kerning_weekly", "percent": 10, "amount": "10.00" }]
}

The hosted checkout

Send the buyer to pay_url (https://aliquot.cc/pay/{id}). The page needs nothing else from you.

  • Network selection. For an AWAITING_SELECTION invoice, the buyer first picks one of the network and token pairs available in the invoice's environment. Picking assigns the deposit address, computes total for that token, and restarts the payment window: the buyer gets the full expires_in_minutes from that moment. A pair cannot be changed once picked.
  • Paying. The buyer sees the exact amount, the address and a QR code (the EIP-681 payment_uri), and can pay from a browser wallet with one button, which switches the wallet to the right network first.
  • Live status. The page follows the invoice as it moves through DETECTED, with a confirmation count, to PAID, and then shows a receipt, with the payment link's fulfilment when there is one. If the buyer underpays, the page asks for the rest; if the invoice expires or a refund is held, the page says so.

The checkout is built on public endpoints you can also call to build your own payment page:

EndpointDescription
GET /api/v1/public/pay/{id}The buyer-safe view of the invoice: amounts, address, confirmations, allowed_pairs, refund status, and fulfillment (null until the invoice is paid; see Payment links). No ledger data, no internal IDs.
POST /api/v1/public/pay/{id}/selectBody { "network", "token" }. Picks the pair of an AWAITING_SELECTION invoice. 409 if a different pair was already picked or the invoice expired.
GET /api/v1/public/pay/{id}/streamServer-sent events. An invoice event carries the same view as GET /api/v1/public/pay/{id} on every change. The stream closes when the invoice reaches PAID, OVERPAID, EXPIRED, EXPIRED_UNDERPAID, REFUNDED or CANCELLED, and after 30 minutes otherwise; EventSource reconnects on its own.
const source = new EventSource('https://api.aliquot.cc/api/v1/public/pay/inv_01JB7Q8M3X4K9TPZ2R6WVN5HDC/stream');
source.addEventListener('invoice', (e) => {
  const view = JSON.parse(e.data);
  console.log(view.status, `${view.confirmations}/${view.required_confirmations}`);
});

Anyone with the invoice ID can open its checkout, so do not treat the ID as a secret, and never fulfil an order from what a browser reports. Fulfil from the invoice.paid webhook or from GET /api/v1/crypto/invoices/{id}.

Returning to your site

When the checkout sees the invoice reach PAID while the buyer has it open, it counts down five seconds and navigates to redirect_url; the buyer can choose to stay. A buyer who opens the page after the fact gets a link back instead, and so does a buyer whose receipt shows a payment link's fulfilment, so they do not miss it. Aliquot does not append anything to the URL, so put whatever you need to find the order in redirect_url itself, for example https://shop.example.com/orders/77310.

Arriving at redirect_url does not prove payment. Check the invoice status on your server.

Payment window

expires_in_minutes (default 30) is the payment window: how long the buyer has to send the money once a deposit address exists.

  • Network and token sent at creation. The window starts at creation: expires_at is the creation time plus expires_in_minutes.
  • The buyer picks the network. expires_at first bounds the choice: the buyer must pick a pair within expires_in_minutes of creation, or the invoice becomes EXPIRED. Picking moves expires_at to the moment of selection plus the same expires_in_minutes, so the buyer always gets the full window to pay, and the whole flow can take up to twice expires_in_minutes.

Invoices created from a payment link use the organization's default window (30 minutes) the same way. An invoice that is still PENDING or AWAITING_SELECTION at expires_at becomes EXPIRED; one that is UNDERPAID becomes EXPIRED_UNDERPAID. A DETECTED invoice never expires, because the buyer paid in time.