RevenueCat Setup
Set up RevenueCat for in-app purchases and subscriptions on iOS and Android — from account creation to sandbox testing.
RevenueCat Setup
RevenueCat handles in-app purchases and subscriptions for both iOS and Android. It wraps Apple's StoreKit and Google Play Billing into a single SDK so you don't have to deal with receipt validation, subscription status tracking, or platform differences.
Why not Stripe? Apple and Google require that digital goods sold within apps use their in-app purchase systems. Stripe can only be used for physical goods or services performed outside the app.
1. Create a RevenueCat Account
- Go to app.revenuecat.com and sign up
- Create a new Project (e.g., "My App")
- Add your platforms:
- Apple App Store — you'll need your App Store Connect credentials
- Google Play Store — you'll need your Google Play Console service account
Apple App Store Configuration
- In RevenueCat, go to Project Settings > Apps > App Store App
- Enter your Bundle ID (must match
app.json→ios.bundleIdentifier) - Add your App Store Connect Shared Secret:
- Go to App Store Connect → Your App → General > App Information > App-Specific Shared Secret
- Copy the secret and paste it in RevenueCat
Google Play Store Configuration
- In RevenueCat, go to Project Settings > Apps > Play Store App
- Enter your Package Name (must match
app.json→android.package) - Upload your Service Account JSON key:
- In Google Play Console → Setup > API Access
- Create a service account with Financial Data permissions
- Download the JSON key and upload it to RevenueCat
2. Set Up Products
In App Store Connect (iOS)
- Go to App Store Connect → Your App → Monetization > Subscriptions
- Create a Subscription Group (e.g., "Premium")
- Add subscription products:
- Monthly — e.g.,
com.yourapp.monthlyat $9.99/month - Annual — e.g.,
com.yourapp.annualat $79.99/year
- Monthly — e.g.,
- Fill in display name, description, and pricing for each product
- Submit for review (products must be approved before they work in sandbox)
In Google Play Console (Android)
- Go to Google Play Console → Your App → Monetize > Products > Subscriptions
- Create subscription products with the same IDs as iOS:
- Monthly —
com.yourapp.monthly - Annual —
com.yourapp.annual
- Monthly —
- Set pricing and billing period for each product
- Activate the products
3. Configure RevenueCat Products
Back in the RevenueCat dashboard:
- Go to Products and add each product ID from both platforms
- Create an Entitlement (e.g., "pro") — this represents what the user unlocks
- Attach your products to the entitlement
- Create an Offering (e.g., "default") — this is what you display to users
- Add Packages to the offering (Monthly, Annual) and link them to your products
4. Install the SDK
npx expo install react-native-purchasesInitialize RevenueCat
Get your API keys from the RevenueCat dashboard (Project Settings > Apps > API Keys):
// lib/revenuecat.ts
import Purchases from "react-native-purchases";
import { Platform } from "react-native";
export async function initRevenueCat() {
Purchases.configure({
apiKey: Platform.OS === "ios"
? "appl_your_ios_api_key"
: "goog_your_android_api_key",
});
}Call this in your root layout:
// app/_layout.tsx
useEffect(() => {
initRevenueCat();
}, []);Identify Users
Link RevenueCat to your authenticated user so subscription status syncs across devices:
// After successful login
const { data: { user } } = await supabase.auth.getUser();
if (user) {
await Purchases.logIn(user.id);
}// After successful login
const userId = useConvexAuth().userId;
if (userId) {
await Purchases.logIn(userId);
}5. Set Up Webhooks
RevenueCat webhooks notify your backend when a subscription changes (renewal, cancellation, expiry, etc.).
- Create a Supabase Edge Function to handle webhook events:
supabase functions new revenuecat-webhook- In the RevenueCat dashboard, go to Integrations > Webhooks
- Set the webhook URL to:
https://your-project.supabase.co/functions/v1/revenuecat-webhook - Add an authorization header for security
- Your Edge Function should update the user's subscription status in your database:
// supabase/functions/revenuecat-webhook/index.ts
Deno.serve(async (req) => {
const body = await req.json();
const event = body.event;
const appUserId = event.app_user_id;
// Update subscription status based on event type
// event.type: INITIAL_PURCHASE, RENEWAL, CANCELLATION, EXPIRATION, etc.
await supabase.from("profiles").update({
plan: event.type === "EXPIRATION" ? "free" : "pro",
revenuecat_app_user_id: appUserId,
}).eq("id", appUserId);
return new Response("ok");
});- Create a Convex HTTP action to handle webhook events
- In the RevenueCat dashboard, go to Integrations > Webhooks
- Set the webhook URL to:
https://your-project.convex.site/revenuecat-webhook - Your HTTP action should update the user's subscription status:
// convex/http.ts
http.route({
path: "/revenuecat-webhook",
method: "POST",
handler: async (ctx, req) => {
const body = await req.json();
const event = body.event;
const appUserId = event.app_user_id;
// Update subscription status based on event type
await ctx.runMutation(internal.subscriptions.updateStatus, {
userId: appUserId,
plan: event.type === "EXPIRATION" ? "free" : "pro",
});
return new Response("ok");
},
});Webhook Event Types
| Event | When it fires |
|---|---|
INITIAL_PURCHASE | First subscription purchase |
RENEWAL | Subscription renewed |
CANCELLATION | User cancelled (still active until period ends) |
EXPIRATION | Subscription expired |
BILLING_ISSUE | Payment failed |
PRODUCT_CHANGE | User changed plan (upgrade/downgrade) |
6. Test in Sandbox Mode
iOS Sandbox Testing
- In App Store Connect, go to Users and Access > Sandbox > Test Accounts
- Create a sandbox tester account (use a real email you can access)
- On your test device: Settings > App Store > Sandbox Account — sign in with the sandbox account
- Run your development build — purchases will use the sandbox environment automatically
- Sandbox subscriptions renew at accelerated rates:
- 1 week → 3 minutes
- 1 month → 5 minutes
- 1 year → 1 hour
Android Sandbox Testing
- In Google Play Console, go to Setup > License Testing
- Add tester Gmail addresses
- Upload an APK/AAB to any track (internal testing works)
- Testers can make purchases without being charged
Verify in RevenueCat
- Make a test purchase on your device
- Check the RevenueCat dashboard → Customers to see the purchase
- Verify your webhook endpoint received the event
- Confirm your database was updated with the correct subscription status
RevenueCat Paywall
Build a paywall screen to display your offerings:
function PaywallScreen() {
const [offerings, setOfferings] = useState<PurchasesOfferings | null>(null);
useEffect(() => {
Purchases.getOfferings().then(setOfferings);
}, []);
return (
<ScreenLayout>
<Text style={styles.title}>Upgrade to Pro</Text>
<Text style={styles.subtitle}>Unlock all features</Text>
{offerings?.current?.availablePackages.map((pkg) => (
<TouchableOpacity key={pkg.identifier} onPress={() => purchaseSubscription(pkg)}>
<Text>{pkg.product.title}</Text>
<Text>{pkg.product.priceString}/month</Text>
</TouchableOpacity>
))}
<Button title="Restore Purchases" variant="ghost" onPress={restorePurchases} />
</ScreenLayout>
);
}Restore Purchases
Required by Apple — users must be able to restore purchases on new devices:
async function restorePurchases() {
try {
const customerInfo = await Purchases.restorePurchases();
const isPro = customerInfo.entitlements.active["pro"] !== undefined;
Alert.alert(isPro ? "Restored!" : "No active subscription found");
} catch (error) {
Alert.alert("Error", "Could not restore purchases");
}
}