Integration
To connect your app, you need RevenueCat and an input field for creator codes. You don't need a separate SDK: you use the RevenueCat SDK you already have.
How it works
- A user enters a creator's code in your app (or arrives via the creator's link).
- Your app checks the code via our API and sets the RevenueCat customer attribute
revreach_code. - RevenueCat sends every purchase event to RevReach via webhook. We recognise the code, attribute the user to the creator and book the revenue share.
- Once a month, we bill: a self-billing invoice to the creator, an invoice for the fee to you, collection via Stripe.
Connect RevenueCat
API key
In RevenueCat, go to Project settings → API keys and create a Secret API key of version V2 with these permissions:
| Area | Permission | Used for |
|---|---|---|
| Charts & Metrics | Read | MRR, active subscriptions, net revenue for the verified numbers |
| Customer information | Read | Looking up the code if it is missing from the webhook |
| Everything else | No access | – |
Enter the key and the project ID in RevReach under Apps → Integration. The key is stored encrypted.
Webhook
- RevenueCat: Integrations → Webhooks → Add new configuration
- URL:
https://revreach.app/api/webhooks/revenuecat/<App-ID>(ready to copy in your dashboard) - Authorization header:
Bearer ups_…(generate it in RevReach; it is only shown once) - Environment: Production and Sandbox. Apps: all apps in the project. Events: all.
- Optional: enable the HMAC signature and store the secret in RevReach.
Webhooks are included in RevenueCat's Pro plan, which is free up to $2,500 in monthly revenue.
Add the code field
Show an optional “Creator code” field during onboarding or on the paywall. Check the code before you set it so that typos are caught and you can tell the user who referred them.
GET https://revreach.app/api/v1/codes/MIRAFIT?app=YOUR_APP_ID
{ "valid": true, "code": "MIRAFIT", "app_id": "…", "creator": { "handle": "mirafit", "name": "Mira" } }
{ "valid": false }You can find the app ID in the dashboard under Apps → Integration in the webhook URL. The API is public and limited to 60 requests per minute per IP address.
Swift
import RevenueCat
struct CreatorCodeResult: Decodable {
let valid: Bool
let code: String?
let creator: Creator?
struct Creator: Decodable { let handle: String; let name: String? }
}
/// Checks the code and stores it in RevenueCat.
func redeemCreatorCode(_ input: String) async throws -> CreatorCodeResult {
let code = input.uppercased().filter { $0.isLetter || $0.isNumber }
var url = URLComponents(string: "https://revreach.app/api/v1/codes/\(code)")!
url.queryItems = [URLQueryItem(name: "app", value: "YOUR_APP_ID")]
let (data, _) = try await URLSession.shared.data(from: url.url!)
let result = try JSONDecoder().decode(CreatorCodeResult.self, from: data)
guard result.valid else { return result }
// The first redeemed code counts: once set, never overwrite it.
guard UserDefaults.standard.string(forKey: "revreach_code") == nil else { return result }
UserDefaults.standard.set(code, forKey: "revreach_code")
Purchases.shared.attribution.setAttributes(["revreach_code": code])
// Optional: send the attributes right away instead of with the next purchase
_ = try? await Purchases.shared.syncAttributesAndOfferingsIfNeeded()
return result
}Kotlin
import android.content.SharedPreferences
import com.revenuecat.purchases.Purchases
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.URL
suspend fun redeemCreatorCode(input: String, prefs: SharedPreferences): Boolean {
val code = input.uppercase().filter { it.isLetterOrDigit() }
val url = URL("https://revreach.app/api/v1/codes/$code?app=YOUR_APP_ID")
val body = withContext(Dispatchers.IO) { url.readText() }
val valid = JSONObject(body).optBoolean("valid", false)
if (!valid) return false
// The first redeemed code counts: once set, never overwrite it.
if (prefs.getString("revreach_code", null) == null) {
prefs.edit().putString("revreach_code", code).apply()
Purchases.sharedInstance.setAttributes(mapOf("revreach_code" to code))
}
return true
}Flutter
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<bool> redeemCreatorCode(String input) async {
final code = input.toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), '');
final res = await http.get(Uri.parse('https://revreach.app/api/v1/codes/$code?app=YOUR_APP_ID'));
final valid = res.statusCode == 200 && jsonDecode(res.body)['valid'] == true;
if (!valid) return false;
// The first redeemed code counts: once set, never overwrite it.
final prefs = await SharedPreferences.getInstance();
if (prefs.getString('revreach_code') == null) {
await prefs.setString('revreach_code', code);
await Purchases.setAttributes({'revreach_code': code});
}
return true;
}React Native
import AsyncStorage from "@react-native-async-storage/async-storage";
import Purchases from "react-native-purchases";
export async function redeemCreatorCode(input: string) {
const code = input.toUpperCase().replace(/[^A-Z0-9]/g, "");
const res = await fetch(`https://revreach.app/api/v1/codes/${code}?app=YOUR_APP_ID`);
const json = await res.json();
// The first redeemed code counts: once set, never overwrite it.
if (json.valid && !(await AsyncStorage.getItem("revreach_code"))) {
await AsyncStorage.setItem("revreach_code", code);
await Purchases.setAttributes({ revreach_code: code });
}
return json;
}Deep link
Creators share links such as https://revreach.app/r/MIRAFIT. The page shows the code for copying and links to the store. If you add a deep link template such as meineapp://code?c={code} under App details, the page also offers “Open app”. Your app reads the code from the link, pre-fills the field and calls the function above.
The stores do not reliably pass on parameters during installation. That is why the entered code is the reliable source.
Attribution rules
- The code that was set first counts. An attribution is never changed retrospectively.
- A new attribution only happens on a first purchase (
INITIAL_PURCHASE,NON_RENEWING_PURCHASE) or when a free trial converts. Renewals by existing subscribers do not trigger a new attribution. - A revenue share accrues on payments within the agreed period from attribution, and only while the partnership is running (plus the tail period, if agreed).
- The basis is net revenue:
price × (1 − tax_percentage − commission_percentage)from the webhook, in euros at the ECB reference rate. - Refunds (
CANCELLATIONwith a negative price) are reversed at the same rate;REFUND_REVERSEDrestores the booking. - Sandbox purchases are displayed but never billed.
- All purchases reported by RevenueCat count: subscriptions (
INITIAL_PURCHASE,RENEWAL), one-time purchases and consumables (NON_RENEWING_PURCHASE). Advertising revenue and the price of a paid download never reach RevenueCat and are not subject to a revenue share. If your app only earns money this way, agree a retainer with creators; the integration is then not required. - For offers with a 0% revenue share (retainer only), purchases made with the code are recorded for analytics but are not subject to a revenue share; the fee is only charged on the retainer.
Testing
- In RevenueCat, click “Send test event” for the webhook. In RevReach, it appears under Integration → Recent webhook events.
- Make a sandbox purchase with the code set. The event appears as “no booking” (sandbox).
- Only production purchases by real users are booked.
Store rules and privacy
- The creator code does not unlock any features and is not a means of payment. To give users with a code a discount, use the stores' tools (Apple offer codes or promotional offers, Google Play offers).
- The code is not personal information about the user. RevenueCat sends us pseudonymous customer IDs and purchase data; we process them as your processor under the data processing agreement.
- Add this disclosure of data to your privacy policy. A suggested wording is provided in the data processing agreement.
- The data is not used for tracking within the meaning of Apple's App Tracking Transparency: no linking with data from other apps, no advertising IDs.