Inspiration
When a subscription payment fails, almost every dunning tool emails everyone. That's wrong in three ways. Some customers would have paid anyway on Stripe's next retry. Some cancel because they were chased. And the ones who actually need help get a generic email that lands in spam. Now that AI voice agents can phone customers, there's a fourth mistake: paying for a call where an email would have worked, since a call costs about 25× more. We wanted an agent that asks the right question: who pays because we acted, and is a call worth its price?
What it does
For each batch of failed Stripe payments, one command runs eight stages:
Detect failed payments using real Stripe decline codes. Qualify: an uplift model estimates who pays because we act, not just who is likely to pay. Decide between doing nothing, sending an email, or placing an AI voice call, by expected value. A call requires consent on file. Measure against five other strategies on held-out data. Govern every action with pure safety rules, budget caps and approval tiers, including two distinct approvers for large amounts. Audit: every decision gets a written brief, and every number in it is checked against evidence. Act: Stripe creates the recovery link, CALL-E calls the customer or Resend emails them, and Slack notifies the operators. Learn: Scar turns repeated call failures into a tested, installable skill.
Results: +$2,521 net margin versus doing nothing, and +$391 versus emailing everyone with 30% fewer contacts. Calling everyone is the worst strategy we tested. The agent calls 38% of customers who need a nudge and 0% of customers who cancel when chased.
How we built it Stripe (test mode): mirrors real declines, reads back Stripe's own decline codes and events, and creates Checkout Session recovery links. It verifies webhook signatures and refuses live keys before sending any request. CALL-E: places the AI recovery call. The call script says it's automated, confirms identity before discussing money, leaves nothing sensitive on voicemail, and honours opt-outs. Code blocks any script that tries to collect card numbers by voice (PCI). Resend sends the recovery email and Slack sends the operator summary. Uplift model (class-variable transformation), with Qini 31.1 on held-out data. Calibration error is 2.97pp across 10 of 10 deciles. Action ledger: idempotency keys are checked before every API call, and failures are recorded word for word with no fake successes. Scar: mines repeated call failures across merchants, writes a skill folder, and refuses to install it until it passes 6 validation checks. A one-file HTML dashboard renders the whole run visually. Zero dependencies and 75 tests. An offline check passes 11/11 with every API key deleted. Challenges we ran into
Our first live CALL-E call to an Indian number was accepted and finished its lifecycle, but the phone never rang. CALL-E's own docs explained why: India uses an International line with a +1 caller ID, and Indian carriers can filter those calls. We retried, and the call reached the account holder, who said "Yeah, go on."
That exposed a second gap: CALL-E's API has no way to check your remaining credit. So we built our own credit cap. It counts every real call the agent places, keeps that count across runs, and refuses to dial once the limit is reached.
We also found a bug in our own safety rules: a warning could run before a hard stop and let a bad action through. We fixed the ordering and added a test for it.
Accomplishments that we're proud of All four apps worked in a live run, each with a real reference ID: Stripe pi_3UFL6t3KGHkj4q0e0eZqDCZI CALL-E call_F-9Tacu-TR1TyLxyTdH7LQ, which reached the account holder with outcome send_link Resend and Slack both delivered Every number in every decision brief traces to evidence. Zero unsupported claims. Honest reporting. We publish our weaknesses (37% of chase-averse customers still get an email) instead of tuning the numbers until they look good. What we learned
Predicting who will pay is the wrong target: that approach contacts people who'd have paid anyway. The real value is in who you leave alone, and a cheap channel needs a minimum-effect floor, or everyone looks worth contacting. We also learned to put safety guards in code, not in prompts.
What's next for Recovery Agent Run it on a real merchant's Stripe data (it already accepts real Stripe events as they are). Run a two-arm test to measure the email-versus-call split instead of assuming it. Get a local caller-ID line for India from CALL-E. Let Scar learn from real call transcripts, not just rehearsals.
Built With
- call-e
- javascript
- node.js
- resend
- slack
- stripe
- typescript
- uplift-modeling
Log in or sign up for Devpost to join the conversation.