thru

Invoices

An invoice bills a named customer for line items. When you send it, it gets a hosted page at https://thru.la/i/<publicId> where the customer picks a chain and pays. It suits business-to-business billing. For selling to your own users, a checkout session or a direct payment is usually a better fit.

Create a draft

bash
curl -X POST https://api.thru.la/v1/invoices \
  -H "x-api-key: $THRU_API_KEY" -H "content-type: application/json" \
  -d '{"customerName":"Example Corp","customerEmail":"billing@example.com","chain":"bnb","network":"mainnet","token":"USDC","lineItems":[{"description":"Consulting, August","quantity":1,"unitAmount":"1500"}],"memo":"Net 15","enabledChains":["bnb","sui"],"successUrl":"https://example.com/invoices/thanks","cancelUrl":"https://example.com/invoices"}'
FieldRules
customerNameRequired, up to 160 characters
customerEmailOptional, up to 200 characters. thru does not email it; you send the link.
chain, tokenRequired. The default chain and the token the invoice is paid in (token up to 64 characters). See supported-chains.
networkOptional; the default is mainnet
lineItemsRequired, at least one { description, quantity, unitAmount }. description is up to 200 characters, quantity a whole number of 1 or more, and unitAmount a decimal string in token units. The total must be more than zero.
enabledChainsOptional, up to 15 chains the customer may choose from, at the same amount
currencyOptional display label, up to 8 characters; the default is "USD". No conversion.
memoOptional, up to 1000 characters
dueAtOptional ISO date, displayed only; nothing happens when it passes
successUrl, cancelUrlOptional http(s) URLs for the links on the hosted page. These links are not signed, so do not treat a visit to them as proof of payment.

The response is the invoice, with id, number (such as INV-0001), publicId, status: "draft" and amount (the total).

Lifecycle

EndpointDoes
PATCH /v1/invoices/:idEdits a draft: customerName, customerEmail, lineItems, memo, dueAt, enabledChains, successUrl, cancelUrl. Chain, token and network cannot be changed.
POST /v1/invoices/:id/sendMoves it to open. The hosted page now accepts payment. Send the customer https://thru.la/i/<publicId>.
POST /v1/invoices/:id/voidMoves it to void. A paid invoice cannot be voided.
GET /v1/invoices/:idOne invoice
GET /v1/invoicesYour newest 200 invoices

Statuses: draftopenpaid, or void.

Payment

When the customer pays on the hosted page, thru creates one payment for the invoice total on the chain they chose (see payments). If the customer reloads the page, they get the same payment again. The public endpoints behind the page are GET /v1/public/invoices/:publicId and POST /v1/public/invoices/:publicId/pay.

That payment expires after 30 minutes like any other, and the invoice keeps pointing at it: the page cannot issue a new address. A transfer that arrives within 7 days of the expiry is still credited automatically, and the invoice becomes paid on the next read; a later one is credited by thru support from its transaction hash, and nothing is replayed to make that happen. A transfer that arrives after the invoice was already paid can be credited the same way — it is an accepted case, not a refused one — and although the payment has been paid out by then, the credit re-opens that payout and the next run forwards the rest (a second settlement.completed, sequence: 2). Only a refunded payment is out of scope. payments describes the window, the recovery and the payout in full. To bill again after that, void the invoice and create a new one.

Knowing that an invoice was paid

There is no invoice webhook. You get the ordinary payment.* events, and the payment carries no invoice id or metadata. Two ways to connect them:

  • Read the invoice: GET /v1/invoices/:id returns status: "paid" once its payment is confirmed or overpaid, and paymentId once the customer has started paying. The status is brought up to date when you read it.
  • On payment.confirmed or payment.overpaid, match data.payment.id against the paymentId of your open invoices.

An underpaid invoice stays open; check its payment's receivedAmount.