Migration guide
Migrate from RevenueCat without losing a subscriber
Import your project, run both systems side by side with forwarded store notifications, then ship one line. Your app code, offerings, API keys and customers stay the same.
Step 1
Import your RevenueCat project
The importer runs on your own machine with your own RevenueCat secret key (read-only is enough). It reads RevenueCat's REST API v2 and writes to your RevenueDot server:
- Apps and their public SDK keys, so shipped builds keep working
- Products, entitlements, offerings and packages, with metadata and the current offering
- Customers, aliases, attributes, subscriptions and one-time purchases
- Current access, so nobody loses a subscription on switch day
It is resumable and idempotent, so a re-run is also the incremental sync during the side-by-side run. Try --dry-run or --limit 50 first. Apple purchases are keyed by their original transaction; Google purchase tokens are recovered from order IDs through Google's Orders API with your own service account.
npx revenuedot import --from-revenuecat \
--rc-key sk_... # RevenueCat secret key, v2, read-only
--rc-project proj... # from the RevenueCat dashboard URL
--to https://api.revenuedot.app \
--to-key sk_... # RevenueDot secret key # Compare active entitlements customer by customer
npx revenuedot import verify --rc-key sk_... --rc-project proj... --to ... --to-key sk_...
# Print the cutover steps with your own app ids and URLs
npx revenuedot import plan --to ... --to-key sk_... Step 2
Run both systems side by side
Change the server notification URLs in App Store Connect and Google Play (Pub/Sub push) to RevenueDot. RevenueDot processes each notification and forwards it unchanged to RevenueCat, so both systems stay accurate while your current app version still talks to RevenueCat.
- Compare customer info and webhooks between the two for as long as you like
- Re-run the importer to pick up anything RevenueCat saw first
- Undo at any time by pointing the notification URLs back
# App Store Connect → App Information → App Store Server Notifications (v2)
https://api.revenuedot.app/v1/notifications/apple/<app id>
# Google Play → Monetization setup → Real-time developer notifications
# Pub/Sub push subscription endpoint
https://api.revenuedot.app/v1/notifications/google/<app id>
# RevenueDot dashboard → Apps → your app → Forwarding URL
# paste the notification URL RevenueCat gave you The dashboard shows each app's exact notification URL and when the last notification arrived.
Step 3
Switch: one line in every SDK
Set the proxy URL before configure in your next release. Self-hosting? Use your own server's URL. When most active users are on the new version, turn RevenueCat off.
// Before Purchases.configure
Purchases.proxyURL = URL(string: "https://api.revenuedot.app")!
Purchases.configure(withAPIKey: "appl_...") // Before Purchases.configure
Purchases.proxyURL = URL("https://api.revenuedot.app") await Purchases.setProxyURL("https://api.revenuedot.app"); await Purchases.setProxyURL("https://api.revenuedot.app"); Purchases.configure({
apiKey: "rcb_...",
appUserId,
httpConfig: { proxyURL: "https://api.revenuedot.app" },
}); await Purchases.setProxyURL({ url: "https://api.revenuedot.app" }); Purchases.proxyURL = "https://api.revenuedot.app" // Inspector: Purchases component → Proxy URL
// https://api.revenuedot.app Purchases.setProxyURL("https://api.revenuedot.app"); Signature checks
With the stock RevenueCat SDK, turn off its response-signature check (Trusted Entitlements), or it reports every RevenueDot response as unverified. On Android, the stock SDK also still sends paywall and ad events to RevenueCat. The RevenueDot forks below fix both.
Or swap the package
MIT forks of all ten RevenueCat SDKs
Each fork keeps RevenueCat's class and method names and every name your code imports. It defaults to api.revenuedot.app, trusts RevenueDot's signing key so entitlements verify, and sends every event to your server.
// Swift Package Manager
.package(url: "https://github.com/revenuedot/purchases-ios", exact: "<version>-revenuedot")
// CocoaPods
pod "RevenueDotPurchases" // still: import RevenueCat Pod RevenueDotPurchases; SPM from github.com/revenuedot/purchases-ios with -revenuedot tags. import RevenueCat stays. revenuedot/purchases-ios
// build.gradle.kts
implementation("app.revenuedot.purchases:purchases:<version>")
// still: import com.revenuecat.purchases.* Maven group app.revenuedot.purchases, same artifact ids. Kotlin packages com.revenuecat.purchases.* stay. revenuedot/purchases-android
// package.json: an npm alias keeps every import
"react-native-purchases": "npm:@revenuedot/react-native-purchases@<version>" npm @revenuedot/react-native-purchases through an alias, so imports do not change. revenuedot/react-native-purchases
# pubspec.yaml
purchases_flutter:
git:
url: https://github.com/revenuedot/purchases-flutter
ref: <version>-revenuedot Git dependency with -revenuedot tags; the package name purchases_flutter stays. The fork also fixes setProxyURL on Flutter web. revenuedot/purchases-flutter
// package.json
"@revenuecat/purchases-js": "npm:@revenuedot/purchases-js@<version>" npm @revenuedot/purchases-js through an alias. The fork also sends analytics events to the proxy URL. revenuedot/purchases-js
// package.json
"@revenuecat/purchases-capacitor": "npm:@revenuedot/purchases-capacitor@<version>" Install through the alias so Capacitor's generated native names stay the same. revenuedot/purchases-capacitor
implementation("app.revenuedot.purchases:purchases-kmp-core:<version>") Maven app.revenuedot.purchases:purchases-kmp-*. Packages com.revenuecat.purchases.kmp.* stay. revenuedot/purchases-kmp
// OpenUPM
openupm add com.revenuedot.purchases-unity OpenUPM com.revenuedot.purchases-unity. using RevenueCat; stays. revenuedot/purchases-unity
cordova plugin add @revenuedot/cordova-plugin-purchases npm @revenuedot/cordova-plugin-purchases. The plugin id and the global Purchases stay. revenuedot/cordova-plugin-purchases
Fork packages publish to their registries with the first release; until then, build from the repositories. The shared layer for the cross-platform SDKs is revenuedot/purchases-hybrid-common.
FAQ
Migration questions
How do I migrate from RevenueCat without losing subscribers?
Run the importer with a read-only RevenueCat secret key to copy your catalog, SDK keys, customers and purchase history. Point App Store and Google Play notifications at RevenueDot, which forwards each one to RevenueCat so both systems stay current. Then ship the one-line proxy change. Current access is imported, so no subscriber loses access on switch day.
Do I have to change my app to switch?
One line. Set the SDK's proxy URL to your RevenueDot server before configure. Offerings, purchases, restores, entitlements and customer info work as before. With the stock SDK you also turn off its response-signature check, or you use a RevenueDot SDK fork, which verifies RevenueDot's signatures.
How do Google Play purchases migrate if RevenueCat doesn't export purchase tokens?
The importer looks up each purchase token from the order IDs in RevenueCat's data through Google's Orders API, using your own Play service account. Renewal notifications and one syncPurchases() call in the app fill any gaps.
Do my existing RevenueCat API keys keep working?
Yes. The importer copies your apps' public SDK key strings (appl_, goog_, rcb_) into RevenueDot, so builds already in the App Store and Google Play authenticate against RevenueDot without a new key.
Will the importer send webhooks for old purchases?
No. Imported history is written directly, with no events and no webhooks, so your backend does not see a flood of old purchases. Pass --emit-events only if you want them.
What does the importer not bring over yet?
Paywalls, targeting rules, experiments, integrations other than webhooks and virtual currency balances. RevenueCat's v2 API does not expose subscription refunds, so a refunded subscription imports as expired.
Which stores and SDK features are supported?
App Store (StoreKit 1 and 2, App Store Server API, Server Notifications v2) and Google Play (Play Developer API, real-time notifications) first. Because RevenueDot serves the API the RevenueCat SDKs call, Expo, StoreKit 2 and current Google Play Billing work through the SDKs you already use. Amazon and Stripe come next.
Can I self-host RevenueCat?
RevenueCat itself is closed source and runs only in RevenueCat's cloud. RevenueDot is a self-hostable server that works with the RevenueCat SDK, so self-hosting means running RevenueDot with Docker and Postgres on your own servers, in the region you choose.
Get started
Try the migration on a copy first.
Run the importer with --dry-run against a self-hosted RevenueDot on your laptop. Nothing changes in RevenueCat or in your app.