Customer.io

Dunning Optimization with Customer.io

How to fix failed payments using Customer.io. Step-by-step implementation guide with real examples.

RD
Ronald Davenport
March 18, 2026
Table of Contents

Why Dunning Fails Without the Right Infrastructure

Most failed payment recovery programs underperform because they treat every failed charge the same way. A card that declined due to insufficient funds needs a different response than one that expired six months ago. Customer.io gives you the event-driven architecture to build that distinction directly into your retry and communication logic.

This guide walks through a complete dunning optimization setup in Customer.io — from pre-dunning alerts before a card fails to multi-step recovery sequences after it does.

---

Understanding the Two-Part Dunning Problem

Involuntary churn from failed payments typically falls into two categories:

  • Soft declines: Temporary issues — insufficient funds, bank-side fraud flags, network errors. These often resolve with a retry 3–5 days later.
  • Hard declines: Permanent issues — expired cards, closed accounts, invalid card numbers. Retrying won't help. You need the customer to take action.

Your Customer.io setup needs to handle both, and it needs to route users into the right path automatically based on the decline type coming from your payment processor.

---

Step 1: Define Your Events and Attributes

Customer.io is an event-driven platform, which means everything starts with the data you send. Before building any campaigns, map out the events your billing system needs to fire.

Required Events

Send these events from your payment processor (Stripe, Brrecurly, Chargebee, etc.) via Customer.io's Track API or a data pipeline like Segment:

  • `payment_failed` — fires immediately on decline. Include properties: `decline_code`, `attempt_number`, `amount`, `plan_name`, `next_retry_date`
  • `payment_succeeded` — fires on successful recovery. Ends the dunning sequence.
  • `card_expiring_soon` — fires 30, 15, and 7 days before expiration for pre-dunning
  • `subscription_canceled` — fires if the account lapses after all retries are exhausted
  • `card_updated` — fires when a customer updates their payment method. Triggers an immediate retry notification.

Required Customer Attributes

Keep these attributes current on each customer profile:

  • `subscription_status` (active, past_due, canceled)
  • `payment_attempts` (integer, incremented on each failure)
  • `last_decline_code` (string from your processor)
  • `card_expiry_month` and `card_expiry_year`

---

Step 2: Build the Pre-Dunning Campaign

Pre-dunning prevents failures before they happen. It's the highest-ROI part of your dunning program because you're communicating with customers who are still active and engaged.

Card Expiry Campaign in Customer.io Campaigns

Use Campaigns (Customer.io's broadcast and triggered campaign builder) with a segment-based trigger:

  1. Create a segment: `card_expiry_month` equals the month two months from now AND `subscription_status` is `active`
  2. Set the campaign to re-evaluate this segment daily
  3. Build a 3-touch email sequence: 60 days out, 30 days out, 7 days out
  4. Each email links directly to your billing portal or a hosted payment update page

The goal is a single-click path to update payment. Friction kills conversion here.

---

Step 3: Build the Post-Failure Recovery Workflow

This is where Customer.io's Journeys (their visual workflow builder) does the heavy work. A Journey lets you branch logic based on event properties, time delays, and customer actions.

Setting Up the Entry Trigger

  • Trigger: `payment_failed` event received
  • Entry condition: `subscription_status` = `past_due`
  • Prevent re-entry until the journey completes

Branching on Decline Type

Inside the Journey, add a branch node immediately after entry. Use the `decline_code` event property to split into two paths:

Getting the most out of Customer.io?

I'll audit your Customer.io setup and show you where revenue is hiding.

Path A — Soft Decline

  1. Wait 1 day, send email: "We had trouble processing your payment — we'll retry shortly"
  2. Wait for `payment_succeeded` event (up to 5 days). If received, exit the journey.
  3. If no success, wait until `next_retry_date`, then send a "Last attempt coming" email
  4. After a final failed retry, push user into Path B logic

Path B — Hard Decline

  1. Send email immediately with a direct link to update payment method
  2. Add an SMS step 48 hours later if the customer has `sms_opted_in` = true
  3. Day 7: send a final "Your subscription is at risk" email with urgency framing
  4. Day 10: fire a `subscription_lapse_warning` internal alert (optional webhook to your CRM)

Winning Condition

Set the journey's goal to the `payment_succeeded` event. Any customer who hits this goal exits the journey immediately — they don't receive additional messages.

---

Step 4: Personalize Messaging with Liquid

Customer.io supports Liquid templating, which lets you pull event properties and customer attributes directly into email copy.

Use `decline_code` to vary the call-to-action text:

  • `card_expired` → "Your card on file has expired. Add a new card to continue your subscription."
  • `insufficient_funds` → "We weren't able to process your payment. Update your billing information or contact your bank."
  • `do_not_honor` → "Your bank declined this charge. Contact your bank or update to a different card."

This single change — specific, accurate language instead of generic "payment failed" copy — measurably improves update rates.

---

Step 5: Measure and Iterate

Track these metrics in Customer.io's Reporting dashboard by campaign and journey:

  • Recovery rate: percentage of `payment_failed` events followed by `payment_succeeded` within 14 days
  • Time to recovery: median days from first failure to successful charge
  • Channel lift: recovery rate for email-only vs. email + SMS paths
  • Drop-off point: which journey step has the highest exit rate without recovery

Run A/B tests on subject lines and send timing using Customer.io's built-in A/B testing within Campaigns.

---

Limitations of Customer.io for Dunning

Customer.io is strong on communication logic but has real gaps in the payment layer:

  • No native retry scheduling. Customer.io can send a message before a retry, but it cannot trigger the retry itself. Your payment processor handles retry timing — Customer.io only reacts to the outcomes.
  • No payment method update UI. You'll need a hosted billing portal (Stripe's customer portal, Chargebee's self-serve page, etc.) for the actual card update. Customer.io just drives traffic there.
  • Segment refresh latency. Segment-based campaign triggers refresh on a schedule, not in real time. For time-sensitive dunning, use event-triggered journeys — not segment campaigns — as your primary mechanism.
  • Limited native reporting on revenue recovered. You'll need to calculate recovered revenue externally or push that data back into Customer.io as a customer attribute.

---

Frequently Asked Questions

How do I prevent customers from receiving dunning emails if they already updated their card?

Set your journey's goal event to `card_updated` or `payment_succeeded`. When either event fires, Customer.io will exit that customer from the active journey immediately. You can also add a filter at each step checking that `subscription_status` is still `past_due` before sending.

Can Customer.io handle multiple subscription products with different retry logic?

Yes. Use the `plan_name` or `product_id` property on your `payment_failed` event to branch within a single journey, or build separate journeys per product and use entry conditions to route customers correctly. Separate journeys give you cleaner reporting.

Most high-performing dunning programs use 4–6 total touchpoints over 10–14 days. Beyond that, you're more likely to increase unsubscribes than recover revenue. If a customer hasn't updated their payment after 6 attempts, the communication problem is not your message cadence — it's your product's perceived value.

What's the best way to handle customers who ignore all dunning messages?

At the end of your journey, fire a webhook to your CRM or support platform flagging the customer for a manual outreach attempt. A single personal email from an account manager recovers a meaningful percentage of high-value accounts that ignored automated sequences entirely.

Related resources

Get the Lifecycle Playbook

One framework per week. No fluff. Unsubscribe anytime.