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

# Expired Invoices

> Handle invoice expiry and give customers a second chance to pay

# Expired Invoices

An invoice expires in one of two ways:

1. The `expiresInMinutes` time limit is reached without full payment
2. The invoice is manually cancelled via `POST /api/v1/invoices/:invoiceId/cancel`

***

## The `invoice.expired` event

```json theme={null}
{
  "event": "invoice.expired",
  "eventId": "evt_a1b2c3d4_invoice.expired_1744453800000",
  "timestamp": "2026-04-12T10:30:00.000Z",
  "data": {
    "invoice": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "amount": "49.99",
      "currency": "USD",
      "settlementCurrency": "USDT",
      "settlementChain": "polygon",
      "status": "expired",
      "createdAt": "2026-04-12T10:00:00.000Z",
      "metadata": { "orderId": "order_123" }
    },
    "fees": {
      "platformFee": "0.00",
      "platformFeePercent": "1.5%",
      "networkFee": "0.00",
      "providerFee": "0.00",
      "totalFees": "0.00",
      "currency": "USDT"
    }
  }
}
```

***

## Handling expiry in your webhook

```javascript theme={null}
case 'invoice.expired': {
  const { invoice } = event.data;
  const orderId = invoice.metadata?.orderId;

  await db.orders.update({
    where: { id: orderId },
    data: { paymentStatus: 'payment_expired' },
  });

  await inventory.release(orderId);

  break;
}
```

***

## Giving the customer a second chance

To let the customer retry, create a new invoice and redirect them to it:

```javascript theme={null}
app.post('/orders/:orderId/retry-payment', async (req, res) => {
  const order = await db.orders.findById(req.params.orderId);

  const response = await fetch('https://api.settlx.io/api/v1/invoices', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SETTLX_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: order.total.toString(),
      currency: 'USD',
      description: `Order #${order.id} (retry)`,
      expiresInMinutes: 30,
      webhookUrl: `${process.env.BASE_URL}/webhooks/settlx`,
      metadata: { orderId: order.id },
    }),
  });

  const { data: newInvoice } = await response.json();

  await db.orders.update({
    where: { id: order.id },
    data: {
      settlxInvoiceId: newInvoice.id,
      paymentStatus: 'awaiting_payment',
    },
  });

  res.redirect(newInvoice.paymentUrl);
});
```

***

## Recommended expiry times

| Use case              | Recommended `expiresInMinutes` |
| --------------------- | ------------------------------ |
| E-commerce checkout   | 15–30                          |
| B2B / invoice payment | 1440–10080 (1–7 days)          |
| Bitcoin payment       | 60 minimum                     |
| No expiry             | Omit `expiresInMinutes`        |

<Note>
  Bitcoin blocks can take 10–60 minutes. Set a minimum of 60 minutes for any invoice that may be paid with BTC to give customers enough time to complete the transaction.
</Note>

***

## Manually cancelling an invoice

Cancel a pending invoice before its natural expiry:

```javascript theme={null}
await fetch(`https://api.settlx.io/api/v1/invoices/${invoiceId}/cancel`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.SETTLX_API_KEY}` },
});
```

Cancellation immediately sets the invoice status to `expired` and fires the `invoice.expired` webhook.

***

## Checkout countdown timer

Always show a countdown so customers know how long they have:

```javascript theme={null}
function startCountdown(expiresAt) {
  const interval = setInterval(() => {
    const remaining = new Date(expiresAt) - Date.now();

    if (remaining <= 0) {
      clearInterval(interval);
      showExpiredUI();
      return;
    }

    const minutes = Math.floor(remaining / 60000);
    const seconds = Math.floor((remaining % 60000) / 1000);

    document.getElementById('timer').textContent =
      `${minutes}:${seconds.toString().padStart(2, '0')}`;
  }, 1000);
}
```

***

## Late payments after expiry

If a customer sends funds to an expired invoice's deposit address:

* The payment is detected on-chain
* It is **not** automatically settled — the invoice is closed
* Contact Settlx support to manually process the payment
