> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tender.cash/llms.txt
> Use this file to discover all available pages before exploring further.

# Payments

> Accept cryptocurrency payments from customers on any supported chain

Tender generates a unique wallet address for each payment request. Your customer sends the exact
crypto amount to that address, Tender detects the on-chain transfer, and marks the transaction
complete. No custodial account or key management is required on your side.

***

## How it works

<Steps>
  <Step title="Initiate a payment">
    Call `POST /payment/initiate` with the amount, chain, and coin. Tender returns a
    `walletAddress` and a `txId` to track the transaction.
  </Step>

  <Step title="Customer sends crypto">
    Display the `walletAddress` and exact `amount` to your customer. They send from their own
    wallet — any compatible wallet on that chain works.
  </Step>

  <Step title="Tender detects the transfer">
    Tender monitors the address on-chain. When the payment arrives, the transaction status
    updates automatically. You can poll or listen via webhook.
  </Step>

  <Step title="Validate the payment">
    Call `POST /payment/validate/{id}` to confirm the payment is received and the transaction
    is complete on your end.
  </Step>
</Steps>

***

## Prerequisites

<Card title="Authentication guide" icon="lock" href="/api-reference/authentication">
  Payment endpoints use Basic (Access ID) authentication — only your `x-access-id` header is
  required. See the authentication guide for details on both auth methods.
</Card>

```javascript theme={null}
const BASE = 'https://sandbox-api.tender.cash/v1/api';
// makeHeaders() → { 'x-access-id': ACCESS_ID, 'Content-Type': 'application/json' }
```

***

## Step 1 — Initiate a payment

<CodeGroup>
  ```javascript Node.js theme={null}
  const res = await fetch(`${BASE}/payment/initiate`, {
    method: 'POST',
    headers: makeHeaders(),
    body: JSON.stringify({
      amount: '10.00',
      chain:  'tron',
      coin:   'usdt',
      reference: 'ORDER-9821', // your own order ID, optional
      meta: { customerEmail: 'customer@example.com' }, // optional metadata
    }),
  });

  const { data: payment } = await res.json();
  console.log(payment);
  /*
  {
    txId:          "66aec7de809b7f45c42a49f9",
    walletAddress: "TQn9Y2khDD9JHTfVE5oB2h8BKWWM4LxKLT",
    amount:        "10.00",
    coin:          "usdt",
    chain:         "tron",
    status:        "pending",
    expiresAt:     "2025-06-10T11:00:00.000Z"
  }
  */

  const { txId, walletAddress, amount } = payment;
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{BASE}/payment/initiate",
      headers=make_headers(),
      json={
          "amount":    "10.00",
          "chain":     "tron",
          "coin":      "usdt",
          "reference": "ORDER-9821",
          "meta":      {"customerEmail": "customer@example.com"},  # optional metadata
      },
  )
  payment = res.json()["data"]
  tx_id         = payment["txId"]
  wallet_address = payment["walletAddress"]
  ```
</CodeGroup>

***

## Step 2 — Show payment details to your customer

Display the wallet address and exact amount. The customer must send **exactly** the specified
amount — partial sends are tracked as partial payments.

```
Please send:

  10.00 USDT
  Network: Tron (TRC-20)
  To:      TQn9Y2khDD9JHTfVE5oB2h8BKWWM4LxKLT

  Do not send from an exchange. Use a self-custody wallet.
  Payment expires in 1 hour.
```

<Warning>
  The `walletAddress` is unique to this transaction. Never reuse addresses across payments.
</Warning>

***

## Step 3 — Poll for payment status

<CodeGroup>
  ```javascript Node.js theme={null}
  async function pollPayment(txId, intervalMs = 8000) {
    while (true) {
      const res = await fetch(`${BASE}/payment/validate/${txId}`, {
        method: 'POST',
        headers: makeHeaders(),
      });
      const { data } = await res.json();

      console.log('Status:', data.status, '| Received:', data.amountReceived);

      if (data.status === 'completed') return data;
      if (data.status === 'failed')    throw new Error('Payment failed');

      await new Promise(r => setTimeout(r, intervalMs));
    }
  }

  const result = await pollPayment(txId);
  ```

  ```python Python theme={null}
  import time

  def poll_payment(tx_id, interval_s=8):
      while True:
          res  = requests.post(f"{BASE}/payment/validate/{tx_id}", headers=make_headers())
          data = res.json()["data"]

          print(f"Status: {data['status']} | Received: {data['amountReceived']}")

          if data["status"] == "completed":
              return data
          if data["status"] == "failed":
              raise RuntimeError("Payment failed")

          time.sleep(interval_s)
  ```
</CodeGroup>

<Tip>
  Use webhooks instead of polling for production. Configure your webhook URL and Tender will push
  a notification the moment the payment status changes.
  See [Webhooks](/get-started/webhooks).
</Tip>

***

## Payment status values

| Status      | Meaning                                       |
| ----------- | --------------------------------------------- |
| `pending`   | Address generated; awaiting customer transfer |
| `partial`   | Some funds received but not the full amount   |
| `completed` | Full amount received and confirmed on-chain   |
| `failed`    | Payment expired or rejected                   |

***

## Partial payments

If the customer sends less than the required `amount`, the transaction moves to `partial` status.
The `balanceRequired` field shows what is still owed. You can:

* Ask the customer to send the remaining balance to the same address
* Mark the order as underpaid and handle it in your own logic

***

## Error handling

| Scenario                    | Action                                                                                               |
| --------------------------- | ---------------------------------------------------------------------------------------------------- |
| Customer sends wrong amount | Check `amountReceived` vs `amount`; handle partial if needed                                         |
| Payment expires             | Initiate a new payment; do not reuse the old address                                                 |
| Wrong network               | Funds sent on the wrong chain cannot be recovered automatically — always display the network clearly |
| `status: "failed"`          | Initiate a fresh payment request                                                                     |
