How do I recover subscribers whose payment failed?
When a renewal fails, the store keeps retrying the charge for days or weeks, but most customers never notice. Payment recovery emails them from your app's name with one link that opens the right place to fix the payment, and shows how much revenue came back.
What counts as a failed payment#
RevenueDot opens a case the moment a subscription gets a billing issue, the same moment it sends the BILLING_ISSUE webhook:
| Store | When a case opens |
|---|---|
| App Store | DID_FAIL_TO_RENEW (billing retry, with or without a grace period) |
| Google Play | SUBSCRIPTION_IN_GRACE_PERIOD or SUBSCRIPTION_ON_HOLD (account hold) |
| Stripe | The subscription goes past_due or unpaid |
| Amazon | The subscription enters its grace period |
| Test Store | The billing_issue scenario (sandbox) |
A case is recovered when the same subscription renews within the recovery window (30 days by default), and lost when the window passes or the purchase is refunded. A billing issue that is already older than the window when RevenueDot first sees it (for example in imported history) opens no case.
Turn it on#
- Open Lifecycle > Payment recovery and select Turn on.
- Check the emails. There are three by default: day 0 (right away), day 3 and day 7 after the payment failed. Each has a subject, heading, body and button label.
{app}becomes your sender name. You can add up to five emails, remove them, or change their days. - Set the sender name (empty: your project's name) and the recovery window (7 to 60 days).
- Select Send test to get one email yourself, then Save.
Emails come from your app's name with your Customer Center support address as Reply-To (Lifecycle > Support > Customer Center). On RevenueDot Cloud they are sent through Cloudflare Email; on a self-hosted server set REVENUEDOT_SMTP_URL and REVENUEDOT_PUBLIC_URL (see Self-hosting).
To try it in sandbox, tick Also email sandbox subscribers and fail a Test Store or Stripe test-mode renewal. Production subscribers always get emails while payment recovery is on.
Who gets an email#
- Customers with an email address: the
$emailattribute, or for Stripe purchases the Stripe customer's email. - Not customers who unsubscribed from your project's emails (payment recovery and win-back campaigns share one unsubscribe list).
- Only while the subscription still has a billing issue. When the store recovers the payment before an email is due, nothing is sent.
- If you turn recovery on while a case already has several emails due, it gets only the latest one.
RevenueDot sends at most 100 recovery emails a minute and 2,000 per project a day. Send due emails on the page sends what is due right away; the server also does this every minute.
Where the link goes#
| Store | The link opens |
|---|---|
| App Store | The customer's Apple ID payment methods page |
| Google Play | The Play Store page of their subscription, where Google shows Fix payment |
| Amazon | Amazon's Memberships and Subscriptions page |
| Stripe | A Stripe customer portal page to update the card, made when they click. If the portal is not set up in your Stripe account, the open invoice's payment page |
A Stripe link opens the portal only while the case is open. After the case is lost, the link says it has expired.
For Stripe, set up the customer portal in your Stripe Dashboard (Settings > Billing > Customer portal). With a restricted key, give it Customer portal: write; with Connect with Stripe nothing else is needed.
Customer Center#
While a customer has an open case, their customer info carries management_url. The RevenueCat SDKs' Customer Center opens it from Manage subscription for purchases it cannot manage itself.
- App Store, Google Play, Amazon: it opens the store's own payment page, like the email.
- Stripe (web): customer info can be read with your app's public key, so this link never opens the billing portal by itself. It shows We'll email you a secure link; the customer taps Email me the link and gets a link at the address on file that works once, for 30 minutes. The portal opens only from that link. At most 3 links an hour per customer and 10 per IP address.
- No email on file: the page says to update the payment in the account where they subscribed on the web, or to contact your support.
Unsubscribe#
Every email has an unsubscribe link. Opening it only asks; the button unsubscribes. When your server's public URL is https, the email also has one-click List-Unsubscribe headers (RFC 8058).
The numbers#
The page shows, for 7, 28 or 90 days, production or sandbox:
- At risk now: open cases and what their periods are worth.
- Emails sent and how many customers opened the link.
- Recovered: subscriptions that renewed after at least one email, and the recovered revenue. This is the money RevenueDot recovered.
- Came back on its own: renewals before any email went out, or with emails off.
- The list of cases, with their status, emails and revenue.
The same numbers are at GET /v2/projects/{project_id}/payment_recovery/stats, and the cases at GET /v2/projects/{project_id}/payment_recovery/cases (API).
Webhooks and integrations#
Nothing changes: BILLING_ISSUE, RENEWAL and EXPIRATION go to your webhooks and integrations as before. If you already email customers from Braze or Customer.io on BILLING_ISSUE, leave payment recovery off or turn one of them off, so customers do not get two emails.