revenuecat-integration

Pushlane: RevenueCat Cancellation Push Notifications

Use RevenueCat CANCELLATION webhooks in Pushlane without confusing auto-renew off, billing errors, refunds and expiration. Build a truthful iOS save flow.

The most important fact about a RevenueCat CANCELLATION event is what it does not necessarily mean: the subscriber may not have lost access.

For a voluntary cancellation, auto-renew is usually off while the current paid or trial period continues. That creates a real window to help the person reconsider, but it also creates an easy way to send misleading copy. "Your subscription has ended" is wrong before expiration. "We canceled your access" may be both wrong and alarming.

A careful cancellation flow needs to know why the event happened before it makes a cause-specific claim. RevenueCat supplies that reason, but Pushlane does not currently project it into flow properties. This tutorial explains what can be automated safely today, what should remain cause-neutral, and when to use a validated custom signal instead.

Cancellation and expiration are separate lifecycle moments

RevenueCat defines CANCELLATION broadly. Its official event types and fields reference says it can represent a canceled or refunded subscription or non-renewing purchase. The cancel_reason field explains why.

For a normal voluntary unsubscribe, cancel_reason is UNSUBSCRIBE. The event fires when the person turns off renewal, not when access expires. RevenueCat sends a later EXPIRATION event when the entitlement should end.

That produces two different messaging jobs:

MomentSubscription stateAppropriate job
CANCELLATION with UNSUBSCRIBEAuto-renew off, access commonly remains until period endConfirm the choice, offer relevant help, allow reactivation
UNCANCELLATIONAuto-renew restored before expirationConfirm that renewal is back on and stop save messages
EXPIRATIONAccess endedStart an honest lapsed-subscriber or reactivation path

Pushlane preserves this distinction in its RevenueCat integration. A cancellation leaves the entitlement active until its expiration timestamp, while expiration marks it expired. The corresponding server-side flow events are revenuecat.cancellation, revenuecat.uncancellation, and revenuecat.expiration.

Do not replace this event model with a client-side "cancel button tapped" event. The actual subscription may be managed outside your app, and RevenueCat's webhook is the server-side record of the lifecycle change.

Understand cancel_reason before sending

Not every CANCELLATION belongs in a cancellation-save campaign.

UNSUBSCRIBE. This is the clearest voluntary cancellation signal. The subscriber commonly retains access through expiration_at_ms. A save flow can acknowledge the choice and offer a relevant reason to turn renewal back on.

BILLING_ERROR. This is involuntary. RevenueCat can dispatch it alongside BILLING_ISSUE when a charge fails. Route it to payment recovery, not to copy that says "Sorry to see you go." Read recovering RevenueCat billing issues with push for that state machine.

CUSTOMER_SUPPORT. This can indicate a refund. RevenueCat warns that a refund does not always mean auto-renew is disabled, so inspect current subscription status rather than inferring the next state from the reason alone. A generic win-back message immediately after a support refund can be inappropriate.

DEVELOPER_INITIATED. Your team or system canceled the subscription. The appropriate communication depends on why. Do not feed it into marketing automation without an operational policy.

PRICE_INCREASE. The subscriber did not consent to a price increase. If you message, explain the actual pricing choice and available options. Do not frame it as unexplained churn.

UNKNOWN. RevenueCat did not receive a specific reason from the store. Use a conservative confirmation or suppress the save flow until current state is known.

The ideal policy is to start a voluntary save sequence only for UNSUBSCRIBE. However, Pushlane's current revenuecat.cancellation flow event does not expose cancel_reason. A Pushlane branch therefore cannot distinguish these reasons today.

That leaves three honest options:

  1. Suppress automatic cancellation copy and use revenuecat.expiration for a later cause-neutral reactivation flow.
  2. Send only a neutral service message that remains true for every cancellation path, such as asking the person to review their current subscription status in the app.
  3. Let your backend validate the reason and current entitlement, then emit a custom event such as subscription_cancelled_voluntarily for a dedicated save flow.

Do not name a cause merely because the raw RevenueCat webhook contains it. A field is usable in Pushlane only when it is actually available to the flow or when your own backend has converted it into a validated signal.

Connect the server event to the right iOS device

RevenueCat sends app_user_id, original_app_user_id, and aliases in webhook identity fields. Pushlane resolves those values to the user identified in the app. The integration works when the same stable account ID is passed to both SDKs:

let userID = Pushlane.identifyForRevenueCat(currentUser.id)
Purchases.configure(withAPIKey: "appl_...", appUserID: userID)

That shared identity connects a server-side cancellation event to the user's current device registration. It is not the APNs token itself. Apple defines a device token as the address of one app on one device, and it can change over the life of an installation. The deeper setup is in matching RevenueCat identities to push tokens.

Pushlane's delivery path is proven on iOS through APNs. Android FCM token registration exists, but Android push delivery is not supported yet. Build and validate this cancellation flow for iOS rather than assuming platform parity.

The RevenueCat webhook must also use Pushlane's dedicated rcw_... authorization secret, never the client SDK write key. The RevenueCat + Pushlane integration guide covers that setup.

Build a cancellation window, not an instant win-back blast

For a voluntary cancellation, the subscriber has already made a choice. Your first notification should confirm that the system understood it and tell them what remains true.

A restrained flow that uses only the fields Pushlane exposes today looks like this:

revenuecat.cancellation
  -> branch: production event and reachable iOS device
  -> send: optional cause-neutral account-status message
  -> wait within the remaining access period
  -> stop if revenuecat.uncancellation arrives
  -> stop or transfer if revenuecat.expiration arrives

For a voluntary save campaign, replace the entry with your validated custom event rather than pretending the generic cancellation event proves an unsubscribe.

Use expiration_at_ms to understand the remaining period, but do not treat the timestamp as a universal license to send at a fixed interval. A monthly subscriber who cancels one day before renewal and an annual subscriber who cancels ten months early should not receive the same schedule.

If the remaining window is short, one confirmation may be enough. If it is long, wait until the message has a real purpose, such as a feature update relevant to the person's previous use or a reminder near the end of access. Do not fill the period with generic "we miss you" pushes.

Copy must match the current state

If your backend has verified a voluntary unsubscribe, the first message can be purely operational:

Renewal is turned off

Your Pro access continues until August 18. You can manage your subscription anytime.

Use an exact date only when you have correctly formatted the real expiration timestamp for the user's locale and confirmed that the event represents a voluntary unsubscribe. Without that verification, keep the message cause-neutral:

Your subscription status changed

Open the app to review your current access and subscription options.

A later save message can lead with retained value:

Your saved plans are still here

Keep Pro active to continue using offline plans after your current period ends.

Good cancellation copy does four things:

  • states that renewal is off rather than claiming access ended;
  • names the date or remaining access only when known;
  • offers a direct, working management destination;
  • gives the person a reason to reconsider without hiding how to leave.

Avoid fake urgency, countdowns that do not use the real expiration time, or a button labeled "Continue" that actually turns renewal back on. Re-enabling a paid renewal must be a clear user choice handled by the store or appropriate subscription management interface.

Apple supports subscription management experiences and links back to the app. Notification interactions can be handled through UNUserNotificationCenterDelegate, as described in Apple's notification action documentation. Verify the destination on a real device. A push that opens a dead route is worse than no push because it confirms the user's decision to leave.

Stop immediately on uncancellation

RevenueCat sends UNCANCELLATION when a canceled, non-expired subscription is re-enabled on supported stores. In Pushlane that becomes revenuecat.uncancellation.

Use it as a hard stop for every pending cancellation message. You may send one confirmation:

Renewal is back on

Your subscription will continue without interruption.

That confirmation has a clear service purpose. A discount or upsell at the same moment does not. The person has already reversed the cancellation, so the save campaign has completed its job.

Webhook arrival order should not be your only protection. Compare lifecycle timestamps and current state before a delayed send. RevenueCat notes in its common webhook flow documentation that network behavior can affect event ordering. Pushlane deduplicates RevenueCat webhook events, but a unique later event can still supersede an earlier cancellation.

Transfer the journey on expiration

If revenuecat.expiration arrives, stop every "you still have access" message. The person is now in a different state.

An expiration flow can say:

Your Pro access has ended

Resubscribe to restore offline plans and unlimited history.

That wording would have been premature on cancellation and is appropriate only after expiration. Keeping separate flows also makes measurement clearer: the pre-expiration flow measures uncancellation or renewal restored; the post-expiration flow measures resubscription.

RevenueCat's event reference says access should be removed on EXPIRATION. It also documents exceptions and store-specific sequences, including billing recovery after an expiration. If a later RENEWAL arrives, stop expired-state reminders and restore the active subscriber path.

Do not let billing failures enter this flow

Billing problems are the most common source of accidental duplicate messaging. RevenueCat may send BILLING_ISSUE and CANCELLATION with BILLING_ERROR together. Without a reason branch, the subscriber could receive both:

  • "Update your payment method";
  • "Sorry to see you go."

Those messages tell two incompatible stories. The first says the system is trying to continue the subscription. The second implies a voluntary choice.

Use revenuecat.billing_issue as the canonical trigger for payment recovery. Because cancel_reason is not exposed in Pushlane flows today, do not use revenuecat.cancellation as a voluntary-churn trigger at the same time. Keep the generic cancellation flow cause-neutral or suppress it, and use a validated custom event when you need an UNSUBSCRIBE-only journey.

Test every reason and transition

A dashboard test webhook proves authentication. A cancellation flow needs sandbox lifecycle tests with unique identities so old aliases do not hide mistakes.

Cover these cases, including the current property limitation:

  1. Generic CANCELLATION with a future expiration. The flow does not claim a reason it cannot inspect.
  2. UNCANCELLATION during a delay. Every pending save message stops.
  3. EXPIRATION during a delay. The user leaves the active-access flow.
  4. BILLING_ISSUE followed by CANCELLATION with BILLING_ERROR. Only the billing recovery flow sends cause-specific copy.
  5. CANCELLATION with CUSTOMER_SUPPORT. The generic flow remains suppressed or cause-neutral.
  6. Duplicate or reordered events. They do not duplicate pushes or revive stale messages.
  7. No reachable iOS device, or an Android-only user. No false sent status is produced.
  8. A tap on a real iPhone. The destination shows the correct subscription state.

The cancellation rule to keep

Treat CANCELLATION as "something changed about the subscription." RevenueCat knows why, but the current Pushlane flow event does not expose that reason. Keep generic automation neutral, use revenuecat.billing_issue for payment recovery, and use a backend-validated custom event for an unsubscribe-only save flow. On expiration, move to a separate cause-neutral reactivation flow.

That event discipline is more important than clever copy. It keeps the notification true at the moment the person reads it, which is the minimum standard for earning another chance after they chose to leave.