Skip to content
revenuecat-integration logo

Revenuecat Integration

revenuecat-integration

Load when integrating RevenueCat for in-app purchases and subscriptions. Applies when implementing iOS/Android subscriptions, entitlement checking, or purchase webhooks in React Native/Flutter.

telum-ai/speck0installs2stars

SKILL.md

Full skill instructions

When This Rule Applies

Apply when implementing subscriptions or in-app purchases in React Native or Flutter apps.


Platform Setup

React Native Installation

npm install react-native-purchases

Android: Set launch mode to prevent purchase interruption:

<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity" android:launchMode="standard" />

iOS: Enable In-App Purchase capability in Xcode.

Initialization

import Purchases from 'react-native-purchases';
import { Platform } from 'react-native';

useEffect(() => {
  Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG);
  
  if (Platform.OS === 'ios') {
    Purchases.configure({ apiKey: 'ios_api_key' });
  } else {
    Purchases.configure({ apiKey: 'android_api_key' });
  }
}, []);

Flutter Initialization

import 'package:purchases_flutter/​purchases_flutter.dart';

await Purchases.setLogLevel(LogLevel.debug);

if (defaultTargetPlatform == TargetPlatform.iOS) {
  await Purchases.configure(PurchasesConfiguration('ios_key'));
} else {
  await Purchases.configure(PurchasesConfiguration('android_key'));
}

Entitlement Checking

React Native Hook

export function useSubscription() {
  const [isSubscribed, setIsSubscribed] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    checkSubscription();
  }, []);

  const checkSubscription = async () => {
    try {
      setLoading(true);
      const info = await Purchases.getCustomerInfo();
      // Check for active entitlement
      setIsSubscribed(!!info.entitlements.active['premium']);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return { isSubscribed, loading, refresh: checkSubscription };
}

Flutter

Future<bool> isPremium() async {
  final info = await Purchases.getCustomerInfo();
  return info.entitlements.active.containsKey('premium');
}

Backend Verification (Security-Critical Operations)

// Always verify on backend for sensitive operations
const response = await fetch(
  `https://api.revenuecat.com/​v1/​subscribers/​${userId}`,
  { headers: { Authorization: `Bearer ${REVENUECAT_API_KEY}` } }
);
const data = await response.json();
const isActive = Object.keys(data.subscriber.entitlements.active).length > 0;

Making Purchases

Display Paywall

export function Paywall() {
  const [offerings, setOfferings] = useState(null);
  const [purchasing, setPurchasing] = useState(false);

  useEffect(() => {
    loadOfferings();
  }, []);

  const loadOfferings = async () => {
    const { current } = await Purchases.getOfferings();
    setOfferings(current);
  };

  const purchase = async (pkg) => {
    try {
      setPurchasing(true);
      const { customerInfo } = await Purchases.purchasePackage(pkg);
      
      if (customerInfo.entitlements.active['premium']) {
        Alert.alert('Success', 'Subscription activated!');
      }
    } catch (error) {
      if (!error.userCancelled) {
        Alert.alert('Error', error.message);
      }
    } finally {
      setPurchasing(false);
    }
  };

  if (!offerings) return <ActivityIndicator />;

  return (
    <View>
      <Button
        title={`Monthly - ${offerings.monthly?.localizedPriceString}`}
        onPress={() => purchase(offerings.monthly)}
        disabled={purchasing}
      />
      <Button
        title={`Annual - ${offerings.annual?.localizedPriceString}`}
        onPress={() => purchase(offerings.annual)}
        disabled={purchasing}
      />
    </​View>
  );
}

Webhook Handling

Express.js Handler

app.post('/​webhooks/​revenuecat', async (req, res) => {
  // Validate auth header
  if (req.headers.authorization !== process.env.REVENUECAT_WEBHOOK_SECRET) {
    return res.status(401).send('Unauthorized');
  }

  const { type, app_user_id } = req.body;

  // Query RevenueCat for authoritative state
  const subscriber = await fetchSubscriber(app_user_id);
  const isActive = Object.keys(subscriber.entitlements.active).length > 0;

  // Update your database
  await db.users.update({
    where: { id: app_user_id },
    data: { isPremium: isActive },
  });

  res.status(200).send('OK');
});

Key Webhook Events

EventWhenAction
INITIAL_PURCHASEFirst subscriptionProvision access
RENEWALAuto-renewedLog retention
BILLING_ISSUE_DETECTEDPayment failedPrompt to update card
SUBSCRIPTION_EXPIREDCancelled + period endedRevoke access
CANCELLATIONUser cancelled (still active)Show retention offer

Idempotency

Webhooks may be delivered multiple times. Track processed events:

const processed = await db.webhookEvents.findOne({ id: event.id });
if (processed) return res.status(200).send('Already processed');

await handleWebhook(event);
await db.webhookEvents.create({ id: event.id });

Sandbox Testing

Two Testing Approaches

ApproachUse CaseLimitations
RevenueCat Test StoreEarly dev, no Apple/​Google setupNo platform-specific features
Platform SandboxesPre-launch, full integrationMetadata may not match production

Testing Workflow

  1. Development: Use Test Store for rapid iteration
  2. Pre-Launch: Switch to platform sandboxes for full integration testing
  3. Never deploy with Test Store API keys

Sandbox Access Control

In RevenueCat Dashboard → Project Settings:

  • Anybody: All sandbox purchases grant entitlements
  • Allowed App User IDs only: Whitelist specific test users
  • Nobody: Track purchases without granting access

Common Gotchas

Android Launch Mode Causes Random Cancellations

When users are redirected to banking apps for payment verification, wrong launch mode cancels purchases:

<!-- MUST be standard or singleTop -->
<activity android:launchMode="standard" />

Entitlement Detachment is Retroactive

Detaching an entitlement from a product removes access for ALL existing customers instantly. Be careful when modifying entitlement-product mappings.

Product ID Naming Convention

Use platform suffixes to avoid confusion:

monthly_ios, yearly_ios
monthly_android, yearly_android

Billing Grace Period (iOS)

Users keep access for ~16 days after failed payment. Check grace_period_expires_date in API response.

Webhook Event Ordering

Events may arrive out of order. Always query RevenueCat API for current state rather than building state from events.


Quick Reference

TaskPattern
Check subscriptioncustomerInfo.entitlements.active['premium']
Make purchasePurchases.purchasePackage(pkg)
Load offeringsPurchases.getOfferings()
Backend verificationGET /​v1/​subscribers/​{id}
Webhook authCheck Authorization header
Test Store → SandboxChange API key before launch

References

More skills from telum-ai

project-brainstorm logo
telum-ai/speck

project-brainstorm

Load when a user has a vague idea, problem, or inspiration but isn't sure what to build. Use before /project-specify when the project concept needs crystallizing from loose thoughts into something concrete.

2 0
View

Load when using Docker for containerizing applications. Applies when creating Dockerfiles, docker-compose setups, multi-stage builds, or optimizing container images.

2 0
View
visual-quality logo
telum-ai/speck

visual-quality

Enforce visual design quality in every UI file. Fires for .tsx, .jsx, .vue, .svelte, and similar component files. Ensures implementations express the project's design personality rather than producing generic/boilerplate UI.

2 0
View
project-clarify logo
telum-ai/speck

project-clarify

Load after project.md exists to fill gaps through targeted Q&A. Use when project goals, target users, or scope feel underspecified. Run before project-context or project-architecture to prevent rework downstream.

2 0
View
story-outline logo
telum-ai/speck

story-outline

DEPRECATED in Speck v7. Use /speck-skeptical-review + /speck-decision-log instead — they replace outline-style research mapping with structured alternative enumeration and decision locking. /story-plan now performs just-in-time research inline. This skill remains for v6 compatibility but redirect...

2 0
View

Load when building offline-first applications that work without network connectivity. Applies when implementing local storage, sync queues, conflict resolution, or background sync.

2 0
View
project-constitution logo
telum-ai/speck

project-constitution

Load (Platform play level only) to define technical principles and architectural guardrails for the project. Produces constitution.md — optional but valuable when a team needs explicit coding standards, governance rules, or non-negotiable constraints. FIRST ACTION after loading: read template at ...

2 0
View
posthog-integration logo
telum-ai/speck

posthog-integration

Load when integrating PostHog for product analytics and feature flags. Applies when implementing event tracking, A/B testing, session replay, or funnel analysis.

2 0
View
project-validate logo
telum-ai/speck

project-validate

Load when all epics are validated and /audit has run for the project. Verifies the project meets its original vision and product-contract.md paid promise. Runs end-to-end JTBD smoke test via /larp across all personas. Declares a project-level readiness state. Produces project-validation-report.md...

2 0
View
story-scan logo
telum-ai/speck

story-scan

Load for brownfield stories where existing code must be understood before planning. Use when a story extends, modifies, or integrates with significant existing functionality. Produces codebase-scan.md — skip for fully greenfield stories. FIRST ACTION after loading: read template at .speck/templat...

2 0
View

Load when building serverless applications with AWS Lambda, Vercel Functions, or Cloudflare Workers. Applies when implementing FaaS patterns, managing cold starts, or optimizing serverless costs.

2 0
View
epic-analyze logo
telum-ai/speck

epic-analyze

Load after epic-tech-spec.md and epic-breakdown.md exist — quality check for consistency and completeness before any story work begins. Catches spec drift and missing coverage between the tech spec and story breakdown. FIRST ACTION after loading: read template at .speck/templates/epic/epic-analys...

2 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View