invoice.failed
Fired when an invoice reaches a non-recoverable failure state. The data.failure_reason field tells you exactly what happened.
Today this event fires for wrong-token deposit resolutions — when a customer paid the deposit address using a different token than the invoice expected, and a Settlx admin processes the resolution. Future failure modes will also use this event with new failure_reason values, so handle the field as an extensible enum.
Failure reasons
data.failure_reason | Meaning |
|---|
wrong_token_refunded | The customer paid the wrong token; Settlx returned the funds to the sender’s address. The merchant did not receive any payment. |
wrong_token_forwarded | The customer paid the wrong token; Settlx forwarded the funds to the merchant’s wallet in the original (mismatched) currency instead of the expected settlement currency. |
For wrong_token_forwarded, the funds in your wallet are in the wrong currency — not your usual settlement currency. Your accounting must handle this case explicitly.
What to do
Branch on data.failure_reason:
wrong_token_refunded
- Mark the order as failed in your system
- Notify the customer that their payment was returned
- Release any reserved inventory
- Do not fulfil the order — no funds arrived
wrong_token_forwarded
- The funds did arrive in your merchant wallet, in whatever asset the customer actually sent
- Decide whether you accept this as fulfilment of the order
- Use
data.withdrawalAmount and data.withdrawalCurrency to record what was received
Payload
{
"event": "invoice.failed",
"eventId": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890_invoice.failed_1744455900000",
"timestamp": "2026-04-12T11:05:00.000Z",
"data": {
"invoice": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"amount": "49.99",
"currency": "USD",
"settlementCurrency": "USDC",
"settlementChain": "polygon",
"status": "expired",
"createdAt": "2026-04-12T10:00:00.000Z",
"metadata": { "orderId": "order_123", "customerId": "cust_456" }
},
"failure_reason": "wrong_token_forwarded",
"withdrawalTxHash": "0x9f4c3a8b7e6d5f4c3a8b7e6d5f4c3a8b7e6d5f4c3a8b7e6d5f4c3a8b7e6d5f4c",
"withdrawalDestination":"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1",
"withdrawalAmount": "49.99",
"withdrawalCurrency": "USDT",
"withdrawalChain": "ethereum"
}
}
| Field | Description |
|---|
data.failure_reason | What went wrong and how it was handled |
data.withdrawalTxHash | On-chain hash of the refund/forward transaction |
data.withdrawalDestination | Address that received the funds (sender for refund, merchant wallet for forward) |
data.withdrawalAmount | Amount that was moved |
data.withdrawalCurrency | Currency of the moved funds — for wrong-token cases this is the wrong token, not your settlement currency |
data.withdrawalChain | Chain the withdrawal occurred on |
Handler example
app.post('/webhooks/settlx', express.raw({ type: 'application/json' }), async (req, res) => {
verifySignature(req);
const event = JSON.parse(req.body);
if (event.event === 'invoice.failed') {
const { invoice, failure_reason, withdrawalAmount, withdrawalCurrency, withdrawalTxHash } = event.data;
const orderId = invoice.metadata?.orderId;
if (failure_reason === 'wrong_token_refunded') {
await db.orders.update(orderId, {
status: 'failed',
failureReason: 'wrong_token_refunded',
refundTxHash: withdrawalTxHash,
});
await notifyCustomer(invoice.metadata?.customerId, {
message: 'Your payment was returned because it was sent in the wrong token.',
});
}
if (failure_reason === 'wrong_token_forwarded') {
await db.orders.update(orderId, {
status: 'received_alt_currency',
receivedAmount: withdrawalAmount,
receivedCurrency: withdrawalCurrency,
settlementTxHash: withdrawalTxHash,
});
// Decide whether to fulfil based on your business rules
await reviewAlternateCurrencyOrder(orderId);
}
}
res.status(200).json({ received: true });
});
Future failure reasons
failure_reason is designed as an extensible enum. New values may be added over time without introducing new event types. Always handle unknown values gracefully — log them and either treat as a generic failure or escalate to manual review.
invoice.settled — The happy path. If you receive invoice.failed you will not receive invoice.settled for the same invoice.
invoice.expired — Different failure mode (timeout, no wrong-token involved).