Skip to main content

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_reasonMeaning
wrong_token_refundedThe customer paid the wrong token; Settlx returned the funds to the sender’s address. The merchant did not receive any payment.
wrong_token_forwardedThe 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"
  }
}
FieldDescription
data.failure_reasonWhat went wrong and how it was handled
data.withdrawalTxHashOn-chain hash of the refund/forward transaction
data.withdrawalDestinationAddress that received the funds (sender for refund, merchant wallet for forward)
data.withdrawalAmountAmount that was moved
data.withdrawalCurrencyCurrency of the moved funds — for wrong-token cases this is the wrong token, not your settlement currency
data.withdrawalChainChain the withdrawal occurred on

Handler example

Node.js
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).