noboarding 0.1.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +515 -0
- package/REVENUECAT_SETUP.md +756 -0
- package/SETUP_GUIDE.md +873 -0
- package/cusomte_screens.md +1964 -0
- package/lib/OnboardingFlow.d.ts +3 -0
- package/lib/OnboardingFlow.js +235 -0
- package/lib/analytics.d.ts +25 -0
- package/lib/analytics.js +72 -0
- package/lib/api.d.ts +31 -0
- package/lib/api.js +149 -0
- package/lib/components/ElementRenderer.d.ts +13 -0
- package/lib/components/ElementRenderer.js +521 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +18 -0
- package/lib/types.d.ts +185 -0
- package/lib/types.js +2 -0
- package/lib/variableUtils.d.ts +17 -0
- package/lib/variableUtils.js +118 -0
- package/logic.md +2095 -0
- package/package.json +44 -0
- package/src/OnboardingFlow.tsx +276 -0
- package/src/analytics.ts +84 -0
- package/src/api.ts +173 -0
- package/src/components/ElementRenderer.tsx +627 -0
- package/src/index.ts +32 -0
- package/src/types.ts +242 -0
- package/src/variableUtils.ts +133 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
# RevenueCat Integration Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to integrating RevenueCat paywalls with Noboarding for seamless paywall analytics and A/B testing.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Overview](#overview)
|
|
8
|
+
2. [Prerequisites](#prerequisites)
|
|
9
|
+
3. [Step 1: Install RevenueCat SDK](#step-1-install-revenuecat-sdk)
|
|
10
|
+
4. [Step 2: Create Custom Paywall Screen](#step-2-create-custom-paywall-screen)
|
|
11
|
+
5. [Step 3: Register Custom Screen](#step-3-register-custom-screen)
|
|
12
|
+
6. [Step 4: Configure Webhooks](#step-4-configure-webhooks)
|
|
13
|
+
7. [Step 5: Add to Dashboard Flow](#step-5-add-to-dashboard-flow)
|
|
14
|
+
8. [Step 6: Test & Deploy](#step-6-test--deploy)
|
|
15
|
+
9. [Analytics & Metrics](#analytics--metrics)
|
|
16
|
+
10. [Troubleshooting](#troubleshooting)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
This integration allows you to:
|
|
23
|
+
- 🎯 **A/B test paywall placement** in your onboarding flow
|
|
24
|
+
- 📊 **Track conversion rates** automatically
|
|
25
|
+
- 💰 **Measure revenue** attributed to onboarding sessions
|
|
26
|
+
- 🔄 **Update flows remotely** without app updates
|
|
27
|
+
|
|
28
|
+
**Architecture:**
|
|
29
|
+
```
|
|
30
|
+
Mobile App (Custom Screen)
|
|
31
|
+
↓ (presents paywall)
|
|
32
|
+
RevenueCat SDK
|
|
33
|
+
↓ (purchase event)
|
|
34
|
+
RevenueCat Backend
|
|
35
|
+
↓ (webhook)
|
|
36
|
+
Supabase Edge Function
|
|
37
|
+
↓ (stores & attributes)
|
|
38
|
+
Dashboard Analytics
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Prerequisites
|
|
44
|
+
|
|
45
|
+
Before starting, ensure you have:
|
|
46
|
+
- ✅ RevenueCat account ([sign up](https://www.revenuecat.com/))
|
|
47
|
+
- ✅ Products configured in App Store Connect / Google Play Console
|
|
48
|
+
- ✅ Products configured in RevenueCat dashboard
|
|
49
|
+
- ✅ Noboarding SDK installed and configured
|
|
50
|
+
- ✅ Supabase project set up
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Step 1: Install RevenueCat SDK
|
|
55
|
+
|
|
56
|
+
### Install the package
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install react-native-purchases
|
|
60
|
+
# or
|
|
61
|
+
yarn add react-native-purchases
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### iOS Setup
|
|
65
|
+
|
|
66
|
+
Add to your `Podfile`:
|
|
67
|
+
```ruby
|
|
68
|
+
pod 'RevenueCat'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then run:
|
|
72
|
+
```bash
|
|
73
|
+
cd ios && pod install
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Android Setup
|
|
77
|
+
|
|
78
|
+
No additional setup needed for Android.
|
|
79
|
+
|
|
80
|
+
### Initialize RevenueCat
|
|
81
|
+
|
|
82
|
+
In your app's root component (usually `App.tsx`):
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { useEffect } from 'react';
|
|
86
|
+
import { Platform } from 'react-native';
|
|
87
|
+
import Purchases from 'react-native-purchases';
|
|
88
|
+
|
|
89
|
+
export default function App() {
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
// Configure RevenueCat
|
|
92
|
+
Purchases.configure({
|
|
93
|
+
apiKey: Platform.OS === 'ios'
|
|
94
|
+
? 'appl_YOUR_IOS_KEY' // Get from RevenueCat dashboard
|
|
95
|
+
: 'goog_YOUR_ANDROID_KEY',
|
|
96
|
+
});
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
// Rest of your app...
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Where to find API keys:**
|
|
104
|
+
1. Go to [RevenueCat Dashboard](https://app.revenuecat.com/)
|
|
105
|
+
2. Select your project
|
|
106
|
+
3. Go to Settings → API Keys
|
|
107
|
+
4. Copy iOS and Android keys
|
|
108
|
+
|
|
109
|
+
### ⚠️ Critical: Sync User IDs
|
|
110
|
+
|
|
111
|
+
**IMPORTANT**: You must use the **same user ID** in both Noboarding SDK and RevenueCat for proper attribution.
|
|
112
|
+
|
|
113
|
+
The Noboarding SDK auto-generates a user ID for analytics. Use the `onUserIdGenerated` callback to sync it with RevenueCat:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { OnboardingFlow } from 'noboarding';
|
|
117
|
+
import Purchases from 'react-native-purchases';
|
|
118
|
+
import { PaywallScreen } from './screens/PaywallScreen';
|
|
119
|
+
|
|
120
|
+
export default function App() {
|
|
121
|
+
const [showOnboarding, setShowOnboarding] = useState(true);
|
|
122
|
+
|
|
123
|
+
if (showOnboarding) {
|
|
124
|
+
return (
|
|
125
|
+
<OnboardingFlow
|
|
126
|
+
apiKey="sk_live_your_api_key"
|
|
127
|
+
customComponents={{
|
|
128
|
+
PaywallScreen: PaywallScreen,
|
|
129
|
+
}}
|
|
130
|
+
// CRITICAL: Sync user ID with RevenueCat
|
|
131
|
+
onUserIdGenerated={(userId) => {
|
|
132
|
+
console.log('SDK User ID:', userId);
|
|
133
|
+
// Use the SAME ID in RevenueCat
|
|
134
|
+
Purchases.logIn(userId);
|
|
135
|
+
}}
|
|
136
|
+
onComplete={(userData) => {
|
|
137
|
+
console.log('Onboarding complete:', userData);
|
|
138
|
+
setShowOnboarding(false);
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return <YourMainApp />;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Why this matters:**
|
|
149
|
+
- When a user purchases, RevenueCat sends `app_user_id` in the webhook
|
|
150
|
+
- Your backend looks up analytics events by `user_id`
|
|
151
|
+
- If the IDs don't match, conversions can't be attributed to onboarding sessions
|
|
152
|
+
- A/B test metrics will be incomplete
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Step 2: Create Custom Paywall Screen
|
|
157
|
+
|
|
158
|
+
Create a new file `screens/PaywallScreen.tsx`:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import React, { useState, useEffect } from 'react';
|
|
162
|
+
import {
|
|
163
|
+
View,
|
|
164
|
+
Text,
|
|
165
|
+
TouchableOpacity,
|
|
166
|
+
ActivityIndicator,
|
|
167
|
+
StyleSheet,
|
|
168
|
+
Alert,
|
|
169
|
+
} from 'react-native';
|
|
170
|
+
import Purchases, { PurchasesOffering, PurchasesPackage } from 'react-native-purchases';
|
|
171
|
+
import type { CustomScreenProps } from 'noboarding';
|
|
172
|
+
|
|
173
|
+
export const PaywallScreen: React.FC<CustomScreenProps> = ({
|
|
174
|
+
analytics,
|
|
175
|
+
onNext,
|
|
176
|
+
onSkip,
|
|
177
|
+
preview,
|
|
178
|
+
onDataUpdate,
|
|
179
|
+
}) => {
|
|
180
|
+
const [loading, setLoading] = useState(true);
|
|
181
|
+
const [purchasing, setPurchasing] = useState(false);
|
|
182
|
+
const [offering, setOffering] = useState<PurchasesOffering | null>(null);
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
analytics.track('paywall_viewed');
|
|
186
|
+
loadOffering();
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const loadOffering = async () => {
|
|
190
|
+
try {
|
|
191
|
+
const offerings = await Purchases.getOfferings();
|
|
192
|
+
setOffering(offerings.current);
|
|
193
|
+
|
|
194
|
+
analytics.track('paywall_loaded', {
|
|
195
|
+
offering_id: offerings.current?.identifier,
|
|
196
|
+
packages_count: offerings.current?.availablePackages.length,
|
|
197
|
+
});
|
|
198
|
+
} catch (error: any) {
|
|
199
|
+
analytics.track('paywall_error', { error: error.message });
|
|
200
|
+
} finally {
|
|
201
|
+
setLoading(false);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const handlePurchase = async (pkg: PurchasesPackage) => {
|
|
206
|
+
try {
|
|
207
|
+
setPurchasing(true);
|
|
208
|
+
|
|
209
|
+
analytics.track('paywall_purchase_started', {
|
|
210
|
+
package_id: pkg.identifier,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const { customerInfo } = await Purchases.purchasePackage(pkg);
|
|
214
|
+
const isPremium = customerInfo.entitlements.active['premium'] !== undefined;
|
|
215
|
+
|
|
216
|
+
if (isPremium) {
|
|
217
|
+
analytics.track('paywall_conversion', {
|
|
218
|
+
package_id: pkg.identifier,
|
|
219
|
+
price: pkg.product.priceString,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
onDataUpdate?.({
|
|
223
|
+
premium: true,
|
|
224
|
+
package_id: pkg.identifier,
|
|
225
|
+
purchase_timestamp: new Date().toISOString(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
Alert.alert('Welcome to Premium!', '', [
|
|
229
|
+
{ text: 'Continue', onPress: onNext }
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
} catch (error: any) {
|
|
233
|
+
const cancelled = error.userCancelled;
|
|
234
|
+
|
|
235
|
+
analytics.track('paywall_purchase_failed', {
|
|
236
|
+
package_id: pkg.identifier,
|
|
237
|
+
cancelled,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!cancelled) {
|
|
241
|
+
Alert.alert('Purchase Failed', 'Please try again.');
|
|
242
|
+
}
|
|
243
|
+
} finally {
|
|
244
|
+
setPurchasing(false);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const handleSkip = () => {
|
|
249
|
+
analytics.track('paywall_dismissed');
|
|
250
|
+
onSkip?.() || onNext();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// PREVIEW MODE (for dashboard)
|
|
254
|
+
if (preview) {
|
|
255
|
+
return (
|
|
256
|
+
<View style={styles.previewContainer}>
|
|
257
|
+
<Text style={styles.previewEmoji}>💎</Text>
|
|
258
|
+
<Text style={styles.previewTitle}>Paywall Preview</Text>
|
|
259
|
+
<Text style={styles.previewNote}>(Real paywall in app)</Text>
|
|
260
|
+
<TouchableOpacity style={styles.button} onPress={onNext}>
|
|
261
|
+
<Text style={styles.buttonText}>Continue</Text>
|
|
262
|
+
</TouchableOpacity>
|
|
263
|
+
</View>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (loading) {
|
|
268
|
+
return (
|
|
269
|
+
<View style={styles.centerContainer}>
|
|
270
|
+
<ActivityIndicator size="large" color="#007AFF" />
|
|
271
|
+
<Text style={styles.loadingText}>Loading options...</Text>
|
|
272
|
+
</View>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<View style={styles.container}>
|
|
278
|
+
<Text style={styles.title}>Unlock Premium</Text>
|
|
279
|
+
<Text style={styles.subtitle}>Get full access to all features</Text>
|
|
280
|
+
|
|
281
|
+
{offering?.availablePackages.map((pkg) => (
|
|
282
|
+
<TouchableOpacity
|
|
283
|
+
key={pkg.identifier}
|
|
284
|
+
style={styles.packageCard}
|
|
285
|
+
onPress={() => handlePurchase(pkg)}
|
|
286
|
+
disabled={purchasing}
|
|
287
|
+
>
|
|
288
|
+
<Text style={styles.packageTitle}>
|
|
289
|
+
{pkg.product.title?.replace(/\(.*?\)/, '').trim()}
|
|
290
|
+
</Text>
|
|
291
|
+
<Text style={styles.packagePrice}>{pkg.product.priceString}</Text>
|
|
292
|
+
</TouchableOpacity>
|
|
293
|
+
))}
|
|
294
|
+
|
|
295
|
+
{purchasing && (
|
|
296
|
+
<View style={styles.purchasingOverlay}>
|
|
297
|
+
<ActivityIndicator size="large" color="#FFFFFF" />
|
|
298
|
+
<Text style={styles.purchasingText}>Processing...</Text>
|
|
299
|
+
</View>
|
|
300
|
+
)}
|
|
301
|
+
|
|
302
|
+
<TouchableOpacity onPress={handleSkip} style={styles.skipButton}>
|
|
303
|
+
<Text style={styles.skipText}>Maybe Later</Text>
|
|
304
|
+
</TouchableOpacity>
|
|
305
|
+
</View>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const styles = StyleSheet.create({
|
|
310
|
+
container: {
|
|
311
|
+
flex: 1,
|
|
312
|
+
padding: 20,
|
|
313
|
+
backgroundColor: '#FFFFFF',
|
|
314
|
+
},
|
|
315
|
+
centerContainer: {
|
|
316
|
+
flex: 1,
|
|
317
|
+
justifyContent: 'center',
|
|
318
|
+
alignItems: 'center',
|
|
319
|
+
},
|
|
320
|
+
previewContainer: {
|
|
321
|
+
flex: 1,
|
|
322
|
+
justifyContent: 'center',
|
|
323
|
+
alignItems: 'center',
|
|
324
|
+
padding: 20,
|
|
325
|
+
backgroundColor: '#F5F5F5',
|
|
326
|
+
},
|
|
327
|
+
previewEmoji: {
|
|
328
|
+
fontSize: 64,
|
|
329
|
+
marginBottom: 16,
|
|
330
|
+
},
|
|
331
|
+
previewTitle: {
|
|
332
|
+
fontSize: 24,
|
|
333
|
+
fontWeight: 'bold',
|
|
334
|
+
marginBottom: 8,
|
|
335
|
+
},
|
|
336
|
+
previewNote: {
|
|
337
|
+
fontSize: 14,
|
|
338
|
+
color: '#999',
|
|
339
|
+
marginBottom: 24,
|
|
340
|
+
},
|
|
341
|
+
title: {
|
|
342
|
+
fontSize: 32,
|
|
343
|
+
fontWeight: 'bold',
|
|
344
|
+
marginBottom: 8,
|
|
345
|
+
textAlign: 'center',
|
|
346
|
+
},
|
|
347
|
+
subtitle: {
|
|
348
|
+
fontSize: 18,
|
|
349
|
+
color: '#666',
|
|
350
|
+
marginBottom: 32,
|
|
351
|
+
textAlign: 'center',
|
|
352
|
+
},
|
|
353
|
+
loadingText: {
|
|
354
|
+
marginTop: 16,
|
|
355
|
+
fontSize: 16,
|
|
356
|
+
color: '#666',
|
|
357
|
+
},
|
|
358
|
+
packageCard: {
|
|
359
|
+
backgroundColor: '#F5F5F5',
|
|
360
|
+
borderRadius: 12,
|
|
361
|
+
padding: 20,
|
|
362
|
+
marginBottom: 12,
|
|
363
|
+
},
|
|
364
|
+
packageTitle: {
|
|
365
|
+
fontSize: 18,
|
|
366
|
+
fontWeight: '600',
|
|
367
|
+
marginBottom: 8,
|
|
368
|
+
},
|
|
369
|
+
packagePrice: {
|
|
370
|
+
fontSize: 24,
|
|
371
|
+
fontWeight: 'bold',
|
|
372
|
+
color: '#007AFF',
|
|
373
|
+
},
|
|
374
|
+
button: {
|
|
375
|
+
backgroundColor: '#007AFF',
|
|
376
|
+
borderRadius: 12,
|
|
377
|
+
padding: 16,
|
|
378
|
+
alignItems: 'center',
|
|
379
|
+
},
|
|
380
|
+
buttonText: {
|
|
381
|
+
color: '#FFFFFF',
|
|
382
|
+
fontSize: 18,
|
|
383
|
+
fontWeight: '600',
|
|
384
|
+
},
|
|
385
|
+
skipButton: {
|
|
386
|
+
marginTop: 16,
|
|
387
|
+
padding: 12,
|
|
388
|
+
alignItems: 'center',
|
|
389
|
+
},
|
|
390
|
+
skipText: {
|
|
391
|
+
fontSize: 16,
|
|
392
|
+
color: '#666',
|
|
393
|
+
},
|
|
394
|
+
purchasingOverlay: {
|
|
395
|
+
...StyleSheet.absoluteFillObject,
|
|
396
|
+
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
397
|
+
justifyContent: 'center',
|
|
398
|
+
alignItems: 'center',
|
|
399
|
+
},
|
|
400
|
+
purchasingText: {
|
|
401
|
+
color: '#FFFFFF',
|
|
402
|
+
marginTop: 16,
|
|
403
|
+
fontSize: 16,
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Step 3: Register Custom Screen
|
|
411
|
+
|
|
412
|
+
In your app's `App.tsx`, register the paywall screen:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import { OnboardingFlow } from 'noboarding';
|
|
416
|
+
import { PaywallScreen } from './screens/PaywallScreen';
|
|
417
|
+
import Purchases from 'react-native-purchases';
|
|
418
|
+
|
|
419
|
+
export default function App() {
|
|
420
|
+
const [showOnboarding, setShowOnboarding] = useState(true);
|
|
421
|
+
|
|
422
|
+
if (showOnboarding) {
|
|
423
|
+
return (
|
|
424
|
+
<OnboardingFlow
|
|
425
|
+
apiKey="sk_live_your_api_key"
|
|
426
|
+
customComponents={{
|
|
427
|
+
PaywallScreen: PaywallScreen, // Register here
|
|
428
|
+
}}
|
|
429
|
+
// CRITICAL: Sync user ID with RevenueCat
|
|
430
|
+
onUserIdGenerated={(userId) => {
|
|
431
|
+
Purchases.logIn(userId);
|
|
432
|
+
}}
|
|
433
|
+
onComplete={(userData) => {
|
|
434
|
+
console.log('Onboarding complete:', userData);
|
|
435
|
+
// userData.premium will be true if they purchased
|
|
436
|
+
setShowOnboarding(false);
|
|
437
|
+
}}
|
|
438
|
+
/>
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return <YourMainApp />;
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Step 4: Configure Webhooks
|
|
449
|
+
|
|
450
|
+
### 4.1 Deploy Supabase Migration
|
|
451
|
+
|
|
452
|
+
Run the migration to create the `revenuecat_events` table:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
cd supabase
|
|
456
|
+
supabase db push
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
This creates the table defined in `migrations/20260219100000_add_revenuecat_events.sql`.
|
|
460
|
+
|
|
461
|
+
### 4.2 Deploy Edge Function
|
|
462
|
+
|
|
463
|
+
Deploy the webhook handler:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
supabase functions deploy revenuecat-webhook
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### 4.3 Set Webhook Secret
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
supabase secrets set REVENUECAT_WEBHOOK_SECRET=your_random_secret_here
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Generate a secure secret:
|
|
476
|
+
```bash
|
|
477
|
+
openssl rand -base64 32
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 4.4 Configure RevenueCat Webhook
|
|
481
|
+
|
|
482
|
+
1. Go to [RevenueCat Dashboard](https://app.revenuecat.com/)
|
|
483
|
+
2. Navigate to **Integrations → Webhooks**
|
|
484
|
+
3. Click **Add New Webhook**
|
|
485
|
+
4. Configure:
|
|
486
|
+
- **URL:** `https://YOUR_PROJECT.supabase.co/functions/v1/revenuecat-webhook`
|
|
487
|
+
- **Authorization Header:** `Bearer your_random_secret_here`
|
|
488
|
+
- **Events to send:** Select at minimum:
|
|
489
|
+
- ✅ INITIAL_PURCHASE
|
|
490
|
+
- ✅ RENEWAL
|
|
491
|
+
- ✅ CANCELLATION
|
|
492
|
+
- ✅ BILLING_ISSUE
|
|
493
|
+
5. Click **Save**
|
|
494
|
+
|
|
495
|
+
### 4.5 Test Webhook
|
|
496
|
+
|
|
497
|
+
RevenueCat provides a test button. Click it to send a test event. Check your Supabase logs:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
supabase functions logs revenuecat-webhook --tail
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
You should see:
|
|
504
|
+
```
|
|
505
|
+
Received RevenueCat event: { type: 'TEST', app_user_id: '...', ... }
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Step 5: Add to Dashboard Flow
|
|
511
|
+
|
|
512
|
+
1. **Log into your dashboard**
|
|
513
|
+
2. **Go to Flows** and select or create a flow
|
|
514
|
+
3. **Click "Add Custom Screen"**
|
|
515
|
+
4. **Enter details:**
|
|
516
|
+
- **Component Name:** `PaywallScreen` (must match exactly)
|
|
517
|
+
- **Description:** "Premium subscription paywall"
|
|
518
|
+
5. **Position the screen** where you want it in the flow
|
|
519
|
+
6. **Save Draft**
|
|
520
|
+
|
|
521
|
+
**Example flow:**
|
|
522
|
+
```
|
|
523
|
+
1. Welcome Screen (SDK)
|
|
524
|
+
2. Feature Tour (SDK)
|
|
525
|
+
3. PaywallScreen (Custom) ← Your paywall
|
|
526
|
+
4. Setup Complete (SDK)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## Step 6: Test & Deploy
|
|
532
|
+
|
|
533
|
+
### Local Testing
|
|
534
|
+
|
|
535
|
+
1. **Test in your app:**
|
|
536
|
+
```bash
|
|
537
|
+
npm start
|
|
538
|
+
# or
|
|
539
|
+
yarn start
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
2. **Navigate to paywall screen**
|
|
543
|
+
3. **Use RevenueCat sandbox mode** for testing:
|
|
544
|
+
- iOS: Use sandbox Apple ID
|
|
545
|
+
- Android: Use test Google account
|
|
546
|
+
|
|
547
|
+
### Production Deployment
|
|
548
|
+
|
|
549
|
+
1. **Build your app:**
|
|
550
|
+
```bash
|
|
551
|
+
# iOS
|
|
552
|
+
npx react-native run-ios --configuration Release
|
|
553
|
+
|
|
554
|
+
# Android
|
|
555
|
+
npx react-native run-android --variant=release
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
2. **Submit to app stores**
|
|
559
|
+
3. **Wait for approval** (1-3 days typically)
|
|
560
|
+
4. **Publish your flow** in the dashboard after app is live
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Analytics & Metrics
|
|
565
|
+
|
|
566
|
+
Once configured, you'll automatically track:
|
|
567
|
+
|
|
568
|
+
### Client-Side Events (from SDK)
|
|
569
|
+
- `paywall_viewed` - User saw the paywall
|
|
570
|
+
- `paywall_loaded` - Offerings loaded successfully
|
|
571
|
+
- `paywall_purchase_started` - User tapped purchase
|
|
572
|
+
- `paywall_conversion` - Purchase completed
|
|
573
|
+
- `paywall_purchase_failed` - Purchase failed
|
|
574
|
+
- `paywall_dismissed` - User skipped paywall
|
|
575
|
+
|
|
576
|
+
### Server-Side Events (from webhook)
|
|
577
|
+
- `paywall_conversion` - Verified purchase from RevenueCat
|
|
578
|
+
- Includes: `product_id`, `price`, `currency`, `transaction_id`
|
|
579
|
+
|
|
580
|
+
### Dashboard Metrics
|
|
581
|
+
|
|
582
|
+
**Analytics Page:**
|
|
583
|
+
- 💎 Paywall Views
|
|
584
|
+
- 💳 Conversions
|
|
585
|
+
- 📊 Conversion Rate
|
|
586
|
+
- 💰 Total Revenue
|
|
587
|
+
|
|
588
|
+
**A/B Tests:**
|
|
589
|
+
- Compare paywall placement
|
|
590
|
+
- Test different offerings
|
|
591
|
+
- Measure impact on conversion
|
|
592
|
+
|
|
593
|
+
### Example Query
|
|
594
|
+
|
|
595
|
+
Get conversion rate by variant:
|
|
596
|
+
|
|
597
|
+
```sql
|
|
598
|
+
SELECT
|
|
599
|
+
variant_id,
|
|
600
|
+
COUNT(DISTINCT CASE WHEN event_name = 'paywall_viewed' THEN user_id END) as views,
|
|
601
|
+
COUNT(DISTINCT CASE WHEN event_name = 'paywall_conversion' THEN user_id END) as conversions,
|
|
602
|
+
ROUND(
|
|
603
|
+
100.0 * COUNT(DISTINCT CASE WHEN event_name = 'paywall_conversion' THEN user_id END) /
|
|
604
|
+
NULLIF(COUNT(DISTINCT CASE WHEN event_name = 'paywall_viewed' THEN user_id END), 0),
|
|
605
|
+
2
|
|
606
|
+
) as conversion_rate
|
|
607
|
+
FROM analytics_events
|
|
608
|
+
WHERE experiment_id = 'your_experiment_id'
|
|
609
|
+
GROUP BY variant_id;
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Troubleshooting
|
|
615
|
+
|
|
616
|
+
### Paywall not showing
|
|
617
|
+
|
|
618
|
+
**Check:**
|
|
619
|
+
1. Component is registered in `customComponents`
|
|
620
|
+
2. Name matches exactly (case-sensitive): `PaywallScreen`
|
|
621
|
+
3. App version includes the custom screen
|
|
622
|
+
4. Dashboard flow is published
|
|
623
|
+
|
|
624
|
+
**Debug:**
|
|
625
|
+
```typescript
|
|
626
|
+
customComponents={{
|
|
627
|
+
PaywallScreen: PaywallScreen,
|
|
628
|
+
}}
|
|
629
|
+
|
|
630
|
+
console.log('Registered:', Object.keys(customComponents));
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### No products available
|
|
634
|
+
|
|
635
|
+
**Check:**
|
|
636
|
+
1. Products configured in App Store Connect / Google Play
|
|
637
|
+
2. Products added to RevenueCat dashboard
|
|
638
|
+
3. Products added to an offering
|
|
639
|
+
4. Using correct entitlement ID
|
|
640
|
+
|
|
641
|
+
**Debug:**
|
|
642
|
+
```typescript
|
|
643
|
+
const offerings = await Purchases.getOfferings();
|
|
644
|
+
console.log('Current offering:', offerings.current);
|
|
645
|
+
console.log('Packages:', offerings.current?.availablePackages);
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Webhook not receiving events
|
|
649
|
+
|
|
650
|
+
**Check:**
|
|
651
|
+
1. URL is correct: `https://YOUR_PROJECT.supabase.co/functions/v1/revenuecat-webhook`
|
|
652
|
+
2. Authorization header is set
|
|
653
|
+
3. Events are selected in RevenueCat
|
|
654
|
+
4. Function is deployed: `supabase functions list`
|
|
655
|
+
|
|
656
|
+
**Debug:**
|
|
657
|
+
```bash
|
|
658
|
+
# View function logs
|
|
659
|
+
supabase functions logs revenuecat-webhook --tail
|
|
660
|
+
|
|
661
|
+
# Test webhook manually
|
|
662
|
+
curl -X POST https://YOUR_PROJECT.supabase.co/functions/v1/revenuecat-webhook \
|
|
663
|
+
-H "Authorization: Bearer your_secret" \
|
|
664
|
+
-H "Content-Type: application/json" \
|
|
665
|
+
-d '{"api_version":"1.0","event":{"id":"test","type":"TEST","app_user_id":"test_user","product_id":"test"}}'
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Conversions not attributed to sessions
|
|
669
|
+
|
|
670
|
+
**Check:**
|
|
671
|
+
1. `app_user_id` in RevenueCat matches `user_id` in analytics events
|
|
672
|
+
2. Purchase happens within 24 hours of onboarding session
|
|
673
|
+
3. Webhook secret is correct
|
|
674
|
+
|
|
675
|
+
**Debug:**
|
|
676
|
+
```sql
|
|
677
|
+
-- Check if user has analytics events
|
|
678
|
+
SELECT * FROM analytics_events
|
|
679
|
+
WHERE user_id = 'your_app_user_id'
|
|
680
|
+
ORDER BY timestamp DESC;
|
|
681
|
+
|
|
682
|
+
-- Check if purchase was recorded
|
|
683
|
+
SELECT * FROM revenuecat_events
|
|
684
|
+
WHERE app_user_id = 'your_app_user_id'
|
|
685
|
+
ORDER BY purchased_at DESC;
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Revenue not showing
|
|
689
|
+
|
|
690
|
+
**Check:**
|
|
691
|
+
1. `price` and `currency` fields are present in webhook payload
|
|
692
|
+
2. Event type is `INITIAL_PURCHASE`
|
|
693
|
+
3. Migration created `revenuecat_events` table
|
|
694
|
+
|
|
695
|
+
**Debug:**
|
|
696
|
+
```sql
|
|
697
|
+
-- Check raw webhook data
|
|
698
|
+
SELECT
|
|
699
|
+
event_type,
|
|
700
|
+
product_id,
|
|
701
|
+
price,
|
|
702
|
+
currency,
|
|
703
|
+
raw_payload
|
|
704
|
+
FROM revenuecat_events
|
|
705
|
+
ORDER BY purchased_at DESC
|
|
706
|
+
LIMIT 10;
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Best Practices
|
|
712
|
+
|
|
713
|
+
### 1. Set User IDs Consistently
|
|
714
|
+
|
|
715
|
+
Ensure the same user ID is used for both:
|
|
716
|
+
- RevenueCat: `Purchases.logIn(userId)`
|
|
717
|
+
- Noboarding analytics: Automatically uses device ID
|
|
718
|
+
|
|
719
|
+
### 2. Handle Edge Cases
|
|
720
|
+
|
|
721
|
+
- Network failures
|
|
722
|
+
- Purchase cancellations
|
|
723
|
+
- Restore purchases flow
|
|
724
|
+
- Family sharing
|
|
725
|
+
|
|
726
|
+
### 3. Test Thoroughly
|
|
727
|
+
|
|
728
|
+
- Test sandbox purchases
|
|
729
|
+
- Test webhook delivery
|
|
730
|
+
- Test analytics attribution
|
|
731
|
+
- Test A/B experiments
|
|
732
|
+
|
|
733
|
+
### 4. Monitor Performance
|
|
734
|
+
|
|
735
|
+
- Track conversion rates
|
|
736
|
+
- Monitor drop-off points
|
|
737
|
+
- A/B test paywall placement
|
|
738
|
+
- Measure revenue impact
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## Next Steps
|
|
743
|
+
|
|
744
|
+
- 📖 Read the [Custom Screens Guide](./cusomte_screens.md)
|
|
745
|
+
- 🎨 Customize your paywall UI
|
|
746
|
+
- 📊 Set up A/B tests in the dashboard
|
|
747
|
+
- 💡 Experiment with paywall placement
|
|
748
|
+
|
|
749
|
+
**Need help?**
|
|
750
|
+
- RevenueCat Docs: https://www.revenuecat.com/docs
|
|
751
|
+
- Noboarding Support: support@noboarding.com
|
|
752
|
+
- Discord Community: [Join here](#)
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
**Happy building! 🚀**
|