How do I add in-app purchases to a Kotlin Multiplatform app with RevenueDot?
Add the RevenueDot SDK, app.revenuedot.purchases:purchases-kmp-core, from Maven Central to commonMain, then call Purchases.configure with your app's key in common code. On RevenueDot Cloud nothing else is needed on Android or iOS.
Install the RevenueDot SDK and pass your key#
Version 3.10.1 is on Maven Central. The source is github.com/revenuedot/purchases-kmp.
// build.gradle.kts, commonMain
implementation("app.revenuedot.purchases:purchases-kmp-core:3.10.1")
implementation("app.revenuedot.purchases:purchases-kmp-ui:3.10.1") // only if you use paywallsimport com.revenuecat.purchases.kmp.Purchases
import com.revenuecat.purchases.kmp.PurchasesConfiguration
fun initPurchases(apiKey: String) {
Purchases.configure(PurchasesConfiguration(apiKey))
}- The RevenueDot SDK is built from RevenueCat's open-source SDK (MIT license), so your code imports
com.revenuecat.purchases.kmp.*and callsPurchases. It sends every request to RevenueDot and needs no RevenueCat account. - Pass the
appl_...key on iOS and thegoog_...key on Android, from RevenueDot. See Which key goes where. - On RevenueDot Cloud the SDK already calls
https://api.revenuedot.app, so there is nothing else to set. - Entitlement verification is off by default (
EntitlementVerificationMode.DISABLED). - Its Android side is RevenueDot's purchases-android 10.22.1 from Maven Central, and its iOS side compiles RevenueDot's purchases-ios 5.90.2. Its iOS library on Maven Central embeds
api.revenuedot.appand RevenueDot's signing key, with no RevenueCat host.
Self-hosting: set Purchases.proxyURL to your server before configure. In purchases-kmp the proxy URL is a String, not a URL, and it applies to both the Android and the iOS targets. Keep the verification mode at DISABLED, because your server signs with its own key, which this build does not trust.
import com.revenuecat.purchases.kmp.models.EntitlementVerificationMode
fun initPurchases(apiKey: String) {
Purchases.proxyURL = "https://revenuedot.example.com"
Purchases.configure(PurchasesConfiguration(apiKey) {
// DISABLED is the KMP default; keep it, because your server signs with its own key.
verificationMode = EntitlementVerificationMode.DISABLED
})
}To verify responses from your own server, build the SDKs with your public key. See Trusted Entitlements.
Check an entitlement and make a purchase#
Check the entitlement your app unlocks, here pro, then buy a package from the current offering, the set of products your paywall shows.
import com.revenuecat.purchases.kmp.ktx.awaitCustomerInfo
import com.revenuecat.purchases.kmp.ktx.awaitOfferings
import com.revenuecat.purchases.kmp.ktx.awaitPurchase
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
val isPro = customerInfo.entitlements.active.containsKey("pro")
val offerings = Purchases.sharedInstance.awaitOfferings()
val pkg = offerings.current?.availablePackages?.firstOrNull() ?: return
val result = Purchases.sharedInstance.awaitPurchase(pkg)
val nowPro = result.customerInfo.entitlements.active.containsKey("pro")awaitPurchase throws PurchasesTransactionException when the purchase fails or the user cancels; check its userCancelled.
Test Store#
Create a test_store app in RevenueDot and pass its test_... key on a debug build. The Test Store dialog replaces the store sheet.
- Native SDKs accept
test_keys only in debug builds. - iOS: servers older than the 2026-09-30 fix could not serve Test Store products to the native iOS SDK. See iOS.
- Test Store prices come from each product's Test Store price. Set it in the dashboard (Product catalog, Edit product) or with
test_store_priceonPOST /v2/projects/{project_id}/products; a product without one shows 0.
More: Test Store.
Switching from RevenueCat? Keep your SDK and change one line#
An app that ships RevenueCat's purchases-kmp can keep it. Set Purchases.proxyURL = "https://revenuedot.example.com" in common code before Purchases.configure (https://api.revenuedot.app on RevenueDot Cloud). The default verification mode is already EntitlementVerificationMode.DISABLED, so nothing else changes on Android or iOS.
import com.revenuecat.purchases.kmp.Purchases
import com.revenuecat.purchases.kmp.PurchasesConfiguration
import com.revenuecat.purchases.kmp.models.EntitlementVerificationMode
fun initPurchases(apiKey: String) {
// Point the SDK at your RevenueDot server; nothing else in the app changes.
Purchases.proxyURL = "https://revenuedot.example.com"
Purchases.configure(PurchasesConfiguration(apiKey) {
// DISABLED is the KMP default; keep it, RevenueDot does not sign responses with RevenueCat's key.
verificationMode = EntitlementVerificationMode.DISABLED
})
}- Set
proxyURLbeforeconfigure. It applies to both the Android and the iOS targets. - Pass the
appl_...key on iOS and thegoog_...key on Android, from RevenueDot or kept by the importer. - Keep entitlement verification off. RevenueCat's SDK checks signatures with RevenueCat's key.
DISABLED, the default, is right for RevenueDot.INFORMATIONALlogs every response as a failed check, andENFORCEDwould fail every request. See Trusted Entitlements. - On Android, RevenueCat's native SDK underneath still sends diagnostics, paywall events and ad events to RevenueCat. See Android.
- Or install the RevenueDot SDK in the same release. Kotlin packages stay
com.revenuecat.purchases.kmp.*, so imports do not change. See Install the RevenueDot SDK.
Sync once on the first launch of the update, so current subscribers keep access:
// Once, after this update: send purchases made while the app talked to RevenueCat.
Purchases.sharedInstance.awaitSyncPurchases()awaitSyncPurchases is a suspend function in com.revenuecat.purchases.kmp.ktx. The callback form is syncPurchases(onError = { }, onSuccess = { }).
The whole app change fits in one diff:
fun initPurchases(apiKey: String) {
+ // Point the SDK at your RevenueDot server; nothing else in the app changes. A String, set before configure.
+ Purchases.proxyURL = "https://revenuedot.example.com"
Purchases.configure(PurchasesConfiguration(apiKey) {
+ // DISABLED is the KMP default; keep it, RevenueDot does not sign responses with RevenueCat's key.
+ verificationMode = EntitlementVerificationMode.DISABLED
})
+ // Once, after this update (from a coroutine): send purchases made while the app talked to RevenueCat.
+ // Purchases.sharedInstance.awaitSyncPurchases()
}The full order of steps is in Migrate from RevenueCat.
Examples#
There is no Kotlin Multiplatform example yet. The calls match the Android guide.