Skip to content

Invoices

An invoice is a request for one payment. It says what is owed and which assets on which networks you accept, and it comes with a hosted checkout page where your customer pays.

Use an invoice when you know the amount in advance: an order, a subscription period, a top-up of a fixed size. For payments of any amount at any time, use a reusable deposit address instead.

Part Meaning
Price For an invoice priced in a currency: the currency and the amount, for example 49.99 USD. Invoices can be priced in USD only for now. The amount is written as a decimal string with up to 8 decimals, such as "49.99".
Options The ways to pay that you accept. Each option is one asset on one network, for example USDT on BNB Smart Chain or ETH on Ethereum. If you list none, the invoice accepts every asset you have enabled.
Checkout URL checkout_url, the page to send your customer to.
Your references order_id, description and metadata (up to 20 keys of your own data), stored with the invoice and returned in every event about it.
Customer email customer_email, optional. It is for your own records: it is returned on the invoice and in its events. It is not shown on the checkout page, and cTpeWay sends no email to it.
Redirect URLs success_url and cancel_url, where the checkout sends the customer afterwards. See Hosted checkout.

Priced in a currency. You give a price, such as 49.99 USD, and list the options you accept. No crypto amount exists yet. When the payer chooses an option on the checkout page, cTpeWay converts the price at the current rate and gives that option a quote: the amount to send, which holds for a limited time. If the payer switches to another option, that option gets its own quote and its own address.

Priced in crypto. You leave the price out and give each option its own exact amount, for example 25 USDT on Tron. The amounts never change.

Either way, every option has its own address, created when the option is first selected. The option keeps that address. A payment to an invoice is always in one asset on one network.

A converted amount would have a long tail of decimals. So a quote is rounded up to a short, readable number of decimals, and the rounded amount is the amount due. The checkout page shows exactly that amount, and a payer who sends exactly the amount shown has paid the invoice in full.

On the invoice, each quoted option carries amount_due, in the asset’s smallest unit, and display_decimals, the number of decimals the amount is stated with. For an invoice priced in crypto the amount is exact, so display_decimals equals the asset’s decimals.

An invoice has one status. These are the values the API and the webhook events use.

Status Meaning What to do
open Created. Waiting for the payer to choose an option and pay. Nothing yet.
pending A payment has been seen on the network but is not confirmed. Wait. Do not fulfil the order yet.
paid Confirmed payments cover the amount due. Fulfil the order.
underpaid Confirmed payments are less than the amount due. The payer can send the rest to the same address until the invoice expires. Or you accept what was paid: see Underpaid invoices.
expired Time ran out before the invoice was paid. Create a new invoice if the customer still wants to pay.
canceled You canceled it. Nothing.
refunded Everything that was paid went back to the payer. This is final. Nothing.

An invoice can also carry outcome_flags, which add detail without changing the status:

Flag Meaning
overpaid The payer sent more than the amount due. The invoice is paid, and the checkout page tells the payer that you have been told about the extra amount.
late A payment was confirmed after the invoice had expired or been canceled. See Late payments.
underpaid_accepted You accepted less than the amount due as payment in full.
partially_refunded Part of what was paid went back to the payer. The invoice keeps its status.
  1. A new invoice is open.
  2. When a payment is detected, it becomes pending.
  3. When the payment is confirmed, cTpeWay compares what arrived with the amount due:
    • enough: paid, with the flag overpaid if it was more;
    • not enough: underpaid. If the rest arrives before the invoice expires, it becomes paid.
  4. An open or underpaid invoice becomes expired when its time runs out. A pending invoice is given extra time for its payment to confirm.

Several payments to the same invoice add up.

Payers sometimes send slightly too little, for example when an exchange deducts a withdrawal charge. You can set a tolerance on an invoice so that a small shortfall still counts as paid. The default is none, and the most you can allow is 2% (200 basis points, tolerance_bps).

An invoice has two clocks, and they are separate.

Clock Field When it runs out
The invoice expires_at on the invoice The invoice becomes expired, if it has not been paid.
The quote quote_expires_at on the option Only the quoted amount lapses. The payer takes a new quote at the current rate, on the same address, and the invoice stays open.
  • The invoice’s time starts when you create it. expires_in sets it for one invoice, to anything from 5 minutes to 30 days. Without it, your account’s setting applies. Selecting an option or renewing a quote does not move it.
  • The quote’s time is short, because rates move. Only invoices priced in a currency have quotes. A quote never runs past the invoice’s own expiry.

In the v2 design the defaults are 24 hours for an invoice and 15 minutes for a quote. These defaults can change before release.

An invoice is underpaid when confirmed payments are less than the amount due, by more than your tolerance. Two things can settle it:

  • The payer sends the rest to the same address before the invoice expires. The invoice becomes paid.
  • You accept what arrived. POST/v1/invoices/{id}/accept makes the invoice paid, with the flag underpaid_accepted. The shortfall is written off.

A payer can send money after an invoice has expired or been canceled. cTpeWay still records the payment, and the funds are settled to you like any other deposit, including the platform fee. The invoice keeps its status, and you receive invoice.late_payment.

You then choose:

The same call accepts an underpayment and a late payment. POST/v1/invoices/{id}/accept_late_payment also exists; it accepts a late payment only.

Rarely, a network reorganises and a payment that was seen disappears. cTpeWay then re-evaluates the invoice without that payment and sends invoice.payment_reverted. This is why you fulfil on paid, never on pending. See Payments and confirmations.

A refund returns one payment, or part of it, to the payer. It is a transfer from your master wallet to an address the payer gives, and it can be made once the payment has been released to your master wallet. An invoice that was paid with several payments is refunded payment by payment.

You create a refund with POST/v1/refunds. The request needs an Idempotency-Key header, and the refunds of a payment cannot add up to more than the payment. A refund is paid as a payout, so the API key needs the refund and payout permissions, and the same limits apply: the payer’s address must be on the key’s payout allowlist, or, for a key limited by IP address alone, the request must come from one of its IP addresses. See Paying out from your master wallet.

When a refund completes, the invoice follows. If everything that was paid has gone back, it becomes refunded and you receive invoice.refunded. If only part has gone back, it keeps its status, gains the flag partially_refunded, and you receive invoice.partially_refunded once, for the first such refund. Every refund also has its own refund.completed event.

cTpeWay charges no platform fee on a refund. The 0.5% platform fee charged on the original deposit is not returned.

Invoice, payment and settlement are three separate things

Section titled “Invoice, payment and settlement are three separate things”
  • The invoice status says whether your customer has paid.
  • Each payment has its own status: detected, confirmed, or removed from the network. See Payments and confirmations.
  • Each confirmed payment has a settlement, which says whether the funds have reached your master wallet. See Settlement and the platform fee.

An invoice can be paid while its settlement is held. Your customer has paid, so you fulfil the order. Held only means the funds have not moved to your master wallet yet.

Endpoint What it does
POST/v1/invoices Create an invoice.
GET/v1/invoices/{id} Retrieve one invoice.
GET/v1/invoices List invoices.
POST/v1/invoices/{id}/cancel Cancel an invoice that is open or underpaid. A payment that still arrives is recorded as a late payment.
POST/v1/invoices/{id}/accept Accept what was paid as payment in full: an underpayment or a late payment.
POST/v1/invoices/{id}/accept_late_payment Accept a late payment only.
GET/v1/invoices/{id}/payments List the payments made to an invoice.
Event Sent when
invoice.created An invoice was created.
invoice.option_selected The payer chose how to pay. The option now has an address and, for an invoice priced in a currency, a locked amount.
invoice.payment_detected A payment was seen on the network. Not final yet.
invoice.underpaid Confirmed payments are below the amount due.
invoice.paid The invoice is paid. Fulfil the order.
invoice.expired The invoice expired unpaid.
invoice.canceled You canceled the invoice.
invoice.late_payment A payment was confirmed after the invoice expired or was canceled.
invoice.payment_reverted A payment was removed from the network and the invoice was re-evaluated without it.
invoice.partially_refunded Part of what was paid went back to the payer. Sent once, when the first such refund completes.
invoice.refunded Everything that was paid went back to the payer.