Expired Invoices
An invoice expires in one of two ways:
- The
expiresInMinutes time limit is reached without full payment
- The invoice is manually cancelled via
POST /api/v1/invoices/:invoiceId/cancel
The invoice.expired event
{
"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
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:
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 |
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.
Manually cancelling an invoice
Cancel a pending invoice before its natural expiry:
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:
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