VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Guides
Launch
Go-live checklist
Make your first App Store purchase
Make your first Google Play purchase
Troubleshoot
Troubleshooting
Recover from incidents
SDK error codes
Purchase error codes
API error codes
FAQ
Recipes
Win back subscribers
Handle plan changes
Unlock the app after a web purchase
Link accounts across platforms
Handle refunds
Register devices for push
Migrate
Migrate to Voidhash
Migrate from RevenueCat
Migrate from Adapty
Migrate from Superwall
Run in observer mode
Privacy and hosting
Delete personal data
Self-hosting
Operate a self-hosted server

Win back subscribers

Offer a discount to people who turn off renewal or whose subscription ended.

A win-back offer is a discounted price, such as 50% off three months, shown to someone who turned off renewal or whose subscription ended. You create the discount in each store, map it to your product as an offer, and present it when Voidhash reports the cancellation.

What you need

  • A subscription product, such as pro_monthly, mapped on the App Store and Google Play.
  • The App Store's In-App Purchase key in your App Store settings; Voidhash signs promotional offers with it. See Connect the App Store (Help center).
  • A way to reach the person: your app, a push notification or your own email.

Create the offer in each store

  • App Store: add a promotional offer to the subscription in App Store Connect. Apple shows promotional offers only to people who have subscribed before, which fits win-back.
  • Google Play: add a developer offer to the base plan, with eligibility you choose in Play Console.

Map it to your product

In Studio, open the product, edit its App Store mapping and add a row under Promotional offers; edit its Google Play mapping and add a row under Offers. Give both rows the same Offer ID, such as winback_50, and enter each store's own offer identifier beside it.

Decide when to offer it

Voidhash records $subscription.canceled when someone turns off renewal and $subscription.expired when access ends. React to them in one of three ways:

  • In the app: when a returning person has no active premium perk, show the offer on your upgrade screen. The store decides whether they are eligible.
  • With an automation: start a flow on the Event trigger $subscription.canceled, wait two days, check the subscription is still not renewing, and send a push that opens your offer screen. Add $subscription.auto_renew_resumed under Leave the flow on so nobody who resubscribes gets the push. See Automations.
  • From your backend: subscribe a webhook to subscription.cancelled and subscription.expired, and send your own email.

Automations and push notifications are in early access.

Buy the offer

Pass the offer ID to the SDK's purchase call:

import Voidhash
import VoidhashCore

func buyWinBack(_ product: VoidhashProduct) async {
    do {
        try await Voidhash.shared?.purchase(product: product, offerId: "winback_50")
    } catch let error as VoidhashStoreError where error.code == "OFFER_UNAVAILABLE" {
        // Not eligible: show the regular price instead.
    } catch {
        print("Purchase failed: \(error)")
    }
}
import com.voidhash.sdk.VoidhashException

try {
    voidhash.purchase(activity, product, offerId = "winback_50")
} catch (error: VoidhashException) {
    if (error.code == "OFFER_UNAVAILABLE") showRegularPrice() else reportError(error)
}
const result = await voidhash.client.purchase(product, { offerId: "winback_50" });

if (result.isErr() && result.error.code === "OFFER_UNAVAILABLE") showRegularPrice();

A paywall can sell the offer as well: an AI agent connected to the MCP server can set the offer on the paywall's purchase. A paywall build fails if no product mapping lists the offer.

When the offer can't be bought

Buying an offer either buys exactly that offer or fails with OFFER_UNAVAILABLE. It never falls back to the regular price, so the person is never charged more than you showed. It fails when:

  • the store doesn't offer it to this person, for example because they never subscribed (App Store) or don't meet the eligibility you set (Google Play);
  • the product's mapping on this store lists no such offer ID;
  • the App Store isn't set up to sign offers, for example without an In-App Purchase key.

Handle it by showing the regular price, or nothing. Don't retry the same offer.

Check that it worked

A redeemed offer is an ordinary purchase of the product, so its perks return as usual. The webhook follows the store's rules: an App Store resubscription continues the old subscription with subscription.renewed, while Google Play starts a new one with subscription.created. Voidhash also records $subscription.offer_redeemed for App Store redemptions. Test the whole path with a sandbox account that has an expired subscription; see Test purchases (Help center).

Stripe and the web

Stripe discounts are coupons and promotion codes on the Checkout Session your server creates, not Voidhash offers. Create the session with the discount and the buyer's voidhash_distinct_id; see Where Voidhash reads the ID.

Next steps

  • Offers: offers in detail.
  • Subscription lifecycle: when each event fires.
  • Automations

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousFAQNextHandle plan changes

On this page

What you needCreate the offer in each storeMap it to your productDecide when to offer itBuy the offerWhen the offer can't be boughtCheck that it workedStripe and the webNext steps