Telemetry
Revenue and billing SQL recipe

Measure Payment-Failure Recovery

Calculate how often failed invoices are recovered by a later successful payment attempt.

Intermediateinvoice_eventsReviewed 2026-07-27Tested with Apache DataFusion 45.2.0

Question answered

Which payment failures recover, and how much revenue remains at risk?

Counting payment failures alone overstates lasting revenue risk. Recovery analysis follows the invoice through later attempts and preserves the amount at risk.

Event contract

Fields the query expects

FieldTypeWhy it exists
timestamp_utcTimestampInvoice event time.
invoice_idUtf8Stable provider invoice identifier.
event_nameUtf8invoice_payment_failed or invoice_paid.
amount_usdFloat64Normalized invoice amount.
failure_typeUtf8Safe categorized failure reason.
DataFusion SQL

Copy the query

sql
WITH failures AS (
  SELECT
    invoice_id,
    MIN(timestamp_utc) AS first_failed_at,
    MAX(amount_usd) AS amount_usd,
    MIN(failure_type) AS failure_type
  FROM invoice_events
  WHERE event_name = 'invoice_payment_failed'
    AND timestamp_utc >= now() - INTERVAL '60 days'
  GROUP BY invoice_id
),
recoveries AS (
  SELECT
    invoice_id,
    MIN(timestamp_utc) AS recovered_at
  FROM invoice_events
  WHERE event_name = 'invoice_paid'
  GROUP BY invoice_id
)
SELECT
  failures.failure_type,
  COUNT(*) AS failed_invoices,
  SUM(CASE
    WHEN recoveries.recovered_at > failures.first_failed_at THEN 1
    ELSE 0
  END) AS recovered_invoices,
  100.0 * SUM(CASE
    WHEN recoveries.recovered_at > failures.first_failed_at THEN 1
    ELSE 0
  END) / NULLIF(COUNT(*), 0) AS recovery_rate_pct,
  SUM(CASE
    WHEN recoveries.recovered_at IS NULL THEN failures.amount_usd
    ELSE 0.0
  END) AS unrecovered_amount_usd
FROM failures
LEFT JOIN recoveries
  ON recoveries.invoice_id = failures.invoice_id
GROUP BY failures.failure_type
ORDER BY unrecovered_amount_usd DESC;

This read-only query is planned and executed against an empty typed table with Apache DataFusion 45.2.0. The deterministic sample output is synthetic and reviewed separately; validate field types, thresholds, and business definitions against your own data. Read the testing methodology.

Query result

Payment recovery rate by failure type

Authentication-required failures recover least often and retain meaningful revenue risk.

failure_typefailed_invoicesrecovered_invoicesrecovery_rate_pctunrecovered_amount_usd
insufficient_funds1428962.686,840
expired_card584781.031,320
authentication_required3618502,940

Synthetic example output. Run the query against your own event schema and thresholds before using it for operational decisions.

How the SQL works

  1. 1The failure CTE collapses repeated provider attempts into one invoice at risk.
  2. 2A recovery counts only when payment succeeds after the first recorded failure.
  3. 3Unrecovered amount complements the rate so a small number of large invoices remains visible.

Edge cases to decide

  • Deduplicate webhook deliveries before treating them as new payment attempts.
  • Refunds and chargebacks need separate lifecycle events after a successful recovery.
  • Normalize currency with a documented rate and retain original currency fields for reconciliation.

Recommended dashboard

  • Bars: recovery_rate_pct by failure_type
  • Stat: unrecovered_amount_usd
  • Table: highest-value unrecovered invoices and account owner

Alert guidance

Alert revenue operations when unrecovered value exceeds a threshold or a normally recoverable failure type deteriorates.

Read alert setup

Put the recipe to work

Related instrumentation and guides

Continue the analysis

Run it on real events

Create a table, adapt the fields, and save the result

Start free, send structured events, and use the query result as a chart, shared dashboard widget, or alert input.

Get an API key