Back to Blog

RevenueCat Webhooks: How to Turn Subscription Events Into Lifecycle Emails

Nic Polotnianko
Co-founder & CEO, Sequenzy
12 min read

RevenueCat is where your app's subscription truth lives, and its webhooks are the cleanest way to get that truth into email. This guide walks through every event that matters for lifecycle email, the email each one should trigger, and the details that trip people up: identity, retries, sandbox events and the trial reminder that RevenueCat does not send.

If you want the strategy first (which flows to build and how email fits with push), start with email marketing for subscription apps. If you just want it working, the Sequenzy RevenueCat integration does everything below with an API key and one webhook.

The events that matter for email

RevenueCat sends about 20 event types. Seven of them drive almost every lifecycle email:

EventWhat it meansEmail to trigger
INITIAL_PURCHASE with period_type: TRIALA free trial startedWelcome and first-value onboarding
INITIAL_PURCHASE (not a trial)A paid subscription startedThank you and what to do next
RENEWAL with is_trial_conversion: trueThe trial converted to paidThank you, next feature
RENEWALA period renewed, or a lapsed user resubscribedUsually nothing; milestone emails at long tenures
BILLING_ISSUEThe store could not charge the renewalPayment recovery
CANCELLATIONAuto-renew turned off, or a refundCancellation survey and save offer
EXPIRATIONAccess endedWin-back

The rest are useful for state but rarely for email: UNCANCELLATION (auto-renew back on, so stop the save flow), PRODUCT_CHANGE (a plan switch, which may take effect later), TRANSFER (purchases moved between app user IDs), SUBSCRIPTION_PAUSED (Google Play only, access continues until the pause starts), SUBSCRIPTION_EXTENDED, NON_RENEWING_PURCHASE, TEMPORARY_ENTITLEMENT_GRANT and the price increase consent events. Ignore TEST events from the dashboard.

The trial reminder RevenueCat cannot send

There is no webhook a few days before a trial converts, because the App Store and Google Play do not send one either. If you want the most valuable email in a subscription app, a reminder before the first charge, you have to schedule it yourself:

  1. On INITIAL_PURCHASE with period_type: TRIAL, read expiration_at_ms.
  2. Schedule a job for 3 days before that time (or 1 day before for weekly trials).
  3. When it runs, check that the user has not already cancelled, converted or been refunded.
  4. Send the reminder.

Sequenzy does this for you: it schedules a saas.trial_will_end event 3 days before the trial ends, and sequences can start from it like any other event.

Reading the fields you need

Identity: who is this event about?

RevenueCat identifies subscribers by app user ID, not email. Every event carries:

  • app_user_id: the last app user ID seen for the subscriber.
  • original_app_user_id: the first one.
  • aliases: every app user ID the subscriber has used.

Check all three against your user records, because a user who started anonymously and later logged in will have both IDs. IDs starting with $RCAnonymousID: belong to users who never logged in, and there is nobody to email unless you collected an address another way.

The easiest way to carry an email is RevenueCat's reserved $email attribute. Set it after sign-in:

Purchases.shared.attribution.setEmail(user.email)

It then appears under subscriber_attributes on later events. Sequenzy checks the external ID match first, then $email, then looks the customer up through the RevenueCat API, then treats an app user ID that is an email address as the email.

Billing details

  • product_id: the store product. Google Play products created after February 2023 look like subscription_id:base_plan_id.
  • period_type: TRIAL, INTRO, NORMAL, PROMOTIONAL or PREPAID.
  • store: APP_STORE, PLAY_STORE, STRIPE, RC_BILLING, AMAZON and others. Use it to write store-specific billing instructions.
  • price_in_purchased_currency and currency: the price in the user's currency. price is USD. Both include the store's commission and tax, so they are not your proceeds.
  • cancel_reason on CANCELLATION and expiration_reason on EXPIRATION: UNSUBSCRIBE, BILLING_ERROR, DEVELOPER_INITIATED, PRICE_INCREASE, CUSTOMER_SUPPORT and UNKNOWN.
  • environment: SANDBOX or PRODUCTION.

Five flows, event by event

Trial welcome

Trigger on INITIAL_PURCHASE with period_type: TRIAL. Send one welcome that names the single action that predicts conversion in your app, then branch on whether the user did it. Stop nudging the moment they do.

Trial ending

Trigger on your scheduled reminder 3 days before expiration_at_ms. Show what they have done in the app, what they keep by staying, the price and how to cancel. Being clear about cancelling reduces refund requests and angry reviews more than it reduces conversions.

Billing issue

Trigger on BILLING_ISSUE. The store keeps retrying, and the user often keeps access during a grace period, so this revenue is very recoverable. Users cannot fix payment inside your app, so link the steps for their store:

  • App Store: Settings, then their name, then Payment and Shipping.
  • Google Play: Play Store, profile, Payments and subscriptions.

Repeat once or twice. End the flow as soon as a RENEWAL arrives, or on EXPIRATION if the retries fail. See failed payment recovery emails for copy patterns.

Cancellation

Trigger on CANCELLATION, but check cancel_reason first:

  • UNSUBSCRIBE: the user turned off auto-renew. Send a one-question survey and a save offer before the period ends.
  • CUSTOMER_SUPPORT: a refund. Do not send a save offer; a short feedback request is enough.
  • BILLING_ERROR: a payment problem, already covered by the billing flow.

Stop the flow on UNCANCELLATION. Our guide to cancellation feedback emails has survey wording that gets replies.

Win-back

Trigger on EXPIRATION where expiration_reason is not BILLING_ERROR (those users did not choose to leave; send a "your subscription ended, here is how to restart" email instead). Wait a week, then send what is new, then an offer. A web checkout link works even if the user deleted the app. More in the win-back email sequence guide.

Handling deliveries correctly

If you build your own webhook server, these rules from RevenueCat's docs matter:

  • Return 200, fast. Only HTTP 200 counts as success. RevenueCat disconnects after 60 seconds and retries up to 5 times, at roughly 2.5, 5, 10, 20 and 40 minutes. Queue the work and respond immediately.
  • Deduplicate on the event id. Retries reuse the same id and event_timestamp_ms, and delivery is at least once, so the same email can go out twice if you do not.
  • Authenticate every request. Set an Authorization header value in RevenueCat and compare it, or turn on HMAC signing and verify X-RevenueCat-Webhook-Signature over the raw body.
  • Filter sandbox. Set the webhook's environment to production, or drop events where environment is SANDBOX, so TestFlight purchases never email real users.
  • Expect delays and new fields. Most events arrive within a minute, but cancellations can take up to about two hours. Ignore unknown event types instead of failing.
  • Fetch state when it matters. For decisions like "is this user still subscribed?", call RevenueCat's REST API rather than trusting the order events arrived in.

Build it or connect it?

A webhook server plus an email API is a reasonable weekend project: verify, queue, deduplicate, match users, schedule trial reminders, track state for stop conditions, and render emails. The ongoing work is the part people underestimate: editing copy without a deploy, adding a push step, A/B testing a subject line, seeing who is in which flow.

With Sequenzy you paste a read-only RevenueCat API key, add one webhook, and each event becomes a tag change, revenue attributes on the contact and an automation trigger. The trial reminder is scheduled for you, retried deliveries are deduplicated (set the webhook to the Production environment so sandbox purchases stay out), and flows can mix email with push and in-app messages through the mobile SDKs. Setup details are in the RevenueCat integration docs, and other tools that connect to RevenueCat are compared in the best email tools with RevenueCat integration.

Choosing between subscription platforms in the first place? Read RevenueCat vs Superwall.

Frequently asked questions

Does RevenueCat send a webhook before a trial ends?

No. RevenueCat sends INITIAL_PURCHASE when the trial starts and RENEWAL with is_trial_conversion when it converts, but nothing in between. Schedule your own reminder from the trial's expiration_at_ms. Sequenzy does this automatically and fires saas.trial_will_end 3 days before the end.

How do I get the user's email from a RevenueCat webhook?

Set the reserved $email subscriber attribute with the SDK's setEmail method or the REST API. It then appears in subscriber_attributes on events. Otherwise, match the app_user_id, original_app_user_id or aliases to the user record in your own database.

What does CANCELLATION mean in RevenueCat?

Usually that the user turned off auto-renew. They keep access until the period ends, which arrives later as EXPIRATION. CANCELLATION is also sent for refunds, with cancel_reason CUSTOMER_SUPPORT, and for some billing failures with cancel_reason BILLING_ERROR.

How does RevenueCat retry failed webhooks?

It treats anything other than HTTP 200 as a failure and retries up to 5 times with exponential backoff, at about 2.5, 5, 10, 20 and 40 minutes. Requests that take longer than 60 seconds are disconnected and retried. Retries reuse the same event id, so deduplicate on it.

Do RevenueCat webhooks need a paid plan?

Webhooks are part of RevenueCat's current Pro plan, which is free until $2,500 in monthly tracked revenue and then 1% of tracked revenue. Some legacy plans without webhook access need to migrate.

Can I send emails from RevenueCat without writing a server?

Yes. Point the webhook at an email platform that understands RevenueCat events. Sequenzy accepts it directly, maps each event to a tag and an automation trigger, and handles retries, deduplication and identity matching.