# Invoice integration guide

This guide walks through a complete invoice integration — from creating an invoice through handling every payment outcome correctly. It covers the **on-chain** and **Binance Pay** payment methods. For the NFT payment method, see the [NFT invoice integration guide](/invoices/nft-invoice-integration-guide.md).

## Before you start

Check these before writing any integration code:

* **Supported currency pairs** — not every `billedCurrency` and `chargedCurrency` combination is valid. Verify yours against the [Listing invoice currency pairs](/invoices/listing-invoice-currency-pairs.md) endpoint.
* **Amount limits** — each `billedCurrency` has a minimum and maximum. Requests outside the range return HTTP 422. Contact Txn support for the limits per currency.
* **Refund handling** — if you want Txn to automatically issue refunds for underpayments, overpayments, or late payments, this is configured at the account level. Contact Txn to set it up before going live. See [Invoice refunds](/invoices/invoice-refunds.md) for the available options.
* **Hosted page vs. custom UI** — decide upfront. The hosted page requires almost no frontend work; a custom UI gives you full control but requires you to handle more edge cases (XRP destination tags, Binance Pay deeplinks, countdown timers).

## Step 1: Create the invoice

Send a `POST` to [`/api/public/v1/invoices`](/invoices/creating-invoice.md) for each payment your end user needs to make. One invoice, one payment attempt.

### Key parameters

**`billedCurrency`** is the currency you display to the end user — typically the currency in which they hold a balance on your platform (e.g. TRY, USD, CNY). It does not have to be a cryptocurrency.

**`chargedCurrency`** and **`payNetwork`** define what the end user actually pays in and on which blockchain network. These must be a supported cryptocurrency/token and network combination — see [Supported currencies](/api-basics/supported-currencies.md) for valid codes.

**`targetCurrency`** is the currency that lands in your Txn account after the payment is converted. It must be a [tradable fiat currency or cryptocurrency](/api-basics/supported-currencies.md#currency-types) — [display fiat currencies](/api-basics/supported-currencies.md#display-fiat) are not accepted here. If omitted, it defaults to `billedCurrency`, which must itself be tradable.

This matters in practice: many common `billedCurrency` values — such as `TRY`, `CNY`, and `BRL` — are display fiat and cannot hold a balance on Txn. If you invoice in one of these currencies, you must supply `targetCurrency` explicitly (for example, `EUR` or `USD`) so Txn knows which account to settle the funds into. Omitting it with a display fiat `billedCurrency` will return an error.

**`reference`** is your internal correlation key. Set it to your order ID, user ID, or any unique value that lets you match the invoice back to a record on your side. It must be unique per invoice. You will see it on every webhook and API response for the invoice's lifetime.

**`successRedirectUrl`** and **`unsuccessRedirectUrl`** apply only to the hosted page flow. They are the URLs Txn redirects the end user to after a successful payment or after the invoice expires or is cancelled.

### What the response gives you

| Field                            | Description                                                             |
| -------------------------------- | ----------------------------------------------------------------------- |
| `data.attributes.address`        | The wallet address the end user must send crypto to                     |
| `data.attributes.amountCharged`  | The exact crypto amount they need to send                               |
| `data.attributes.expiresAt`      | When the exchange rate and invoice expire (20 minutes from creation)    |
| `data.attributes.hostedPageUrl`  | Ready-to-use hosted payment page URL                                    |
| `included[].type(addresses)`     | Full address entity, including the destination tag for XRP              |
| `included[].type(binanceOrders)` | Binance Pay order details, when `paymentMethods` includes `binance_pay` |

{% hint style="warning" %}
The exchange rate is locked for 20 minutes from invoice creation. Create the invoice only when the end user is ready to pay, and present the payment details immediately — do not create the invoice in advance.
{% endhint %}

## Step 2: Present the invoice to the end user

### Option A — Hosted page

Redirect the end user to `hostedPageUrl`. Txn renders the full payment UI: wallet address, QR code, countdown timer, and Binance Pay instructions where applicable. The end user is redirected to your `successRedirectUrl` or `unsuccessRedirectUrl` when the invoice reaches a terminal state.

This is the fastest path to production and requires no frontend work beyond the redirect.

### Option B — Custom UI

Build your own payment screen using the invoice response data. At minimum, display:

* The wallet address and a scannable QR code
* The exact amount to send (`amountCharged` + `chargedCurrency`)
* The payment network by its full name (`networkName`) — for example, "Tron (TRC20)" rather than the network code `ttrx:usdt`. Users select the network in their wallet app when initiating a withdrawal; showing the recognisable display name reduces the risk of them choosing the wrong network and sending funds that cannot be recovered.
* A countdown timer to `expiresAt`

{% hint style="danger" %}
**XRP invoices:** the address is returned in the format `rwCQVZLSMNY6DgMH61317qvH3nHYqm68PF?dt=123456`, where the value after `?dt=` is the **destination tag**. Display the address and the destination tag separately, and ensure both are clearly visible and copyable. If the end user sends XRP without specifying the destination tag, the payment will not be processed.
{% endhint %}

**Binance Pay:** if `paymentMethods` includes `binance_pay`, the `binanceOrders` entity in `included[]` contains:

* `qrcodeLink` — a hosted QR code image to display
* `deeplink` — opens the Binance app directly on mobile
* `universalUrl` — works across both web and mobile browsers

## Step 3: Track invoice status

### Webhooks (recommended)

Configure your webhook endpoint in the Txn dashboard. Txn sends a `POST` request to your endpoint on every `status` change. The payload is the same shape as the [Read invoice](/invoices/reading-invoice.md) response.

Two implementation requirements:

1. **Verify the signature** on every incoming webhook before processing it. See [Webhooks](/api-basics/webhooks.md) for signature verification details.
2. **Process webhooks idempotently.** The same status transition can be delivered more than once. Use the invoice `id` and `status` together as a deduplication key.

See [Invoice webhooks](/invoices/invoice-webhooks.md) for example payloads for each status.

### Polling (alternative)

Poll [Read invoice](/invoices/reading-invoice.md) by invoice `id`. A reasonable interval is every 5–10 seconds while the end user is on the payment screen. Reduce the frequency after `expiresAt` — the invoice can still complete via late payment for up to 7 days, but changes become infrequent.

### Status reference

| `status`     | Final? | What it means                                                             | What to do                                                                      |
| ------------ | ------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `pending`    | No     | Awaiting a crypto transaction                                             | No action needed                                                                |
| `expired`    | **No** | No payment received within 20 minutes                                     | **Do not cancel your order** — late payment is still possible for 7 days        |
| `processing` | No     | Transaction detected, awaiting confirmations                              | Optionally show "payment detected" to the end user                              |
| `on_hold`    | No     | Compliance review in progress                                             | Do not settle — wait for the next status update                                 |
| `completed`  | Yes    | Payment processed and funds credited to your account                      | Settle based on `statusContext` — see [Step 4](#step-4-handle-payment-outcomes) |
| `cancelled`  | Yes    | No payment received within 7 days, or payment rejected under refund rules | Check `coinDeposits` — if non-empty, a refund link may be present               |
| `rejected`   | Yes    | Payment declined by Txn compliance                                        | Do not settle                                                                   |

## Step 4: Handle payment outcomes

The `statusContext` and `paymentStatus` fields give you the full picture of what happened inside a `completed` or `cancelled` status. Handle each combination explicitly — do not fall through to a default case.

### Full payment, on time

`status: completed` · `statusContext: full` · `paymentStatus: on_time`

Happy path. The end user sent the correct amount within 20 minutes. Proceed to [Step 5](#step-5-credit-the-end-user).

### Full payment, late

`status: completed` · `statusContext: full` · `paymentStatus: late`

The end user paid after the invoice expired but within the 7-day monitoring window. Txn processed the payment at the spot exchange rate applicable at the time of receipt. Proceed to credit the end user.

### Underpayment

`status: completed` · `statusContext: underpaid`

The end user sent less crypto than quoted. Txn applied the spot exchange rate to the amount actually received and settled the result to your account. `invoiceTransactions.amountBilled` reflects the actual credited amount — it will be less than the original invoice amount. Credit the end user accordingly.

If refund rules are configured for underpayments, Txn may instead reject the payment and issue a refund link. The invoice status will be `cancelled` in that case — see [Cancelled with a refund](#cancelled-with-a-refund) below.

### Overpayment

`status: completed` · `statusContext: overpaid`

The end user sent more crypto than quoted. Txn applied the spot exchange rate to the full amount received. `invoiceTransactions.amountBilled` reflects what was actually processed — it will exceed the original invoice amount. Credit the end user accordingly.

Depending on your refund configuration, Txn may instead process only the original invoice amount and issue a refund link for the excess, or reject the entire payment. See [Invoice refunds](/invoices/invoice-refunds.md) for the configured behaviour options.

### On hold

`status: on_hold`

Txn's compliance team is reviewing the payment, typically because the source wallet is associated with elevated risk. Do not settle the end user. The invoice will resolve to either `completed` (payment cleared) or `rejected` (payment declined). Wait for the next webhook.

### Rejected

`status: rejected` — final

The payment was declined by Txn compliance. Txn returns the funds to the sender. Do not settle the end user.

### Cancelled with a refund

`status: cancelled` · `coinDeposits` relationship non-empty

A payment was received but rejected under your account's refund rules (underpayment, overpayment, or late payment). The `refundLinks` relationship will contain a payment link entry. Extract the `hostedPageUrl` from the corresponding entity in `included[]` and share it with the end user so they can claim their funds. See [Refunds](#refunds).

### No payment received

`status: cancelled` · `statusContext: unpaid` · `coinDeposits` empty

No crypto was ever detected at the invoice address within the 7-day monitoring window. Safe to treat as abandoned — cancel the corresponding order on your side.

## Step 5: Credit the end user

When the invoice reaches `completed`, read `invoiceTransactions.amountBilled` from the `included[]` array. This is the amount in `billedCurrency` that the end user's payment actually resulted in, and it is the value to credit to the end user's balance on your platform.

In a full, on-time payment, `invoiceTransactions.amountBilled` equals the original invoice amount. In underpayment and overpayment scenarios, Txn applies the spot exchange rate to the crypto actually received, so the value will differ.

{% hint style="warning" %}
Do not use `data.attributes.amountBilled` (the original quoted invoice amount) or `data.attributes.targetAmount` (the amount that lands in your Txn account) for crediting the end user. Both will produce incorrect results in underpayment and overpayment scenarios.
{% endhint %}

For fee reporting: `invoiceTransactions.transactionFee` and `invoiceTransactions.targetTransactionFee` give you the fee breakdown in `billedCurrency` and `targetCurrency` respectively.

## Refunds

When Txn's refund handling is enabled for your account and a qualifying payment comes in (underpaid, overpaid, or late), Txn rejects the payment and creates a refund link instead of crediting your account. The invoice status becomes `cancelled` and the `refundLinks` relationship in the API response and webhook payload contains the refund payment link.

To handle a refund in your integration:

1. Detect `status: cancelled` with a non-empty `refundLinks` relationship.
2. Find the corresponding `paymentLinks` entity in `included[]`.
3. Extract its `hostedPageUrl`.
4. Deliver that URL to your end user — by email, in-app notification, or however you communicate with them.

The end user visits the URL, enters their wallet address, and Txn sends the crypto back to them directly. The refund link can only be used once. Funds are returned in the same currency and on the same network as the original payment. Network and service fees are deducted from the refund amount — if the refund amount is smaller than the combined fees, Txn cannot process the refund.

See [Invoice refunds](/invoices/invoice-refunds.md) for a full breakdown of each scenario and the available configuration options.

## Subsequent payments to a completed invoice address

After an invoice completes, Txn continues to monitor its receiving address. If the end user sends another crypto transaction to the same address — for example, paying twice by mistake — Txn can automatically credit the incoming funds to your crypto account (for example, your USDT account) rather than leaving them unprocessed.

This behaviour is optional and configured at the account level. Contact Txn support to enable it.

When a subsequent payment is credited, it appears as a `coinTransaction` in your account's transaction history. The transaction inherits the `reference` of the original invoice, so your existing reconciliation logic can correlate it back to the originating order using the same reference matching you already apply to invoices. The original invoice is not affected — it remains in its `completed` state.

## Reconciliation

* **Correlate with your records** — the `reference` you set on creation is returned on every invoice response and webhook. Use it to look up the corresponding order or user in your system.
* **Blockchain proof** — `coinTransactions.txHash` (in `included[]`) is the on-chain transaction hash. Store it and share it with the end user if they need confirmation that the payment was sent.
* **Batch reconciliation** — use the [List invoices](/invoices/listing-invoices.md) endpoint with the `created_at_from` and `created_at_to` parameters to pull all invoices in a given date range.
* **Fee accounting** — `invoiceTransactions.transactionFee` and `invoiceTransactions.targetTransactionFee` are available for each completed invoice.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.txn.io/invoices/invoice-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
