Connect subscription providers to revenue analytics.
AppMetricsKit accepts signed provider webhooks from RevenueCat, Adapty, and Superwall, normalizes them into canonical purchase and subscription events, and stores a provider revenue timeline.
Copy app-specific webhook URLs
Open the Billing page in the app, select the app you want to connect, then copy the provider URL and save the provider secret in AppMetricsKit. The appId query parameter tells AppMetricsKit which app should receive the revenue events.
https://appmetricskit.com/api/webhooks/revenuecat?appId=<APP_ID>https://appmetricskit.com/api/webhooks/adapty?appId=<APP_ID>https://appmetricskit.com/api/webhooks/superwall?appId=<APP_ID>Deployment secret
Provider secrets are saved per app in AppMetricsKit. The only deployment-level value is PROVIDER_WEBHOOK_SYNC_SECRET, which must also be set in Convex. If it is omitted, Convex falls back to the Stripe sync secret. RevenueCat and Adapty tokens are stored as hashes; Superwall Svix secrets are encrypted because signature verification needs the raw secret server-side.
PROVIDER_WEBHOOK_SYNC_SECRET=...PROVIDER_SECRET_ENCRYPTION_KEY=... # recommended 32+ characters, set in ConvexRevenueCat
- Endpoint
- /api/webhooks/revenuecat?appId=<APP_ID>
- Verification
- Authorization header equals the value saved for this app
Generate your own Authorization value, save it in AppMetricsKit, then paste the same value in RevenueCat.
Adapty
- Endpoint
- /api/webhooks/adapty?appId=<APP_ID>
- Verification
- Authorization header equals the value saved for this app
Generate your own Authorization value, save it in AppMetricsKit, then paste the same value in Adapty.
Superwall
- Endpoint
- /api/webhooks/superwall?appId=<APP_ID>
- Verification
- Svix signature with the Superwall secret saved for this app
Copy the Svix signing secret from Superwall and save it in AppMetricsKit for this app.
Event mapping
- Initial purchases become
Purchase.completed. - Renewals become
Subscription.renewed. - Cancellations and expirations become
Subscription.cancelled. - Refunds become
Subscription.refundedwith negative revenue. - Billing issues become
Subscription.billingRetry.
Privacy and duplicate tracking
Provider user IDs are hashed before storage. Webhook events also land in the normal event stream, so avoid sending the same purchase manually from the SDK unless you intentionally want separate client-side and server-side records.
StoreKit 2 helper events
If you are not using RevenueCat, Adapty, or Superwall, map StoreKit 2 purchase and renewal outcomes into the same canonical events.
// StoreKit 2 helper mappingAppMetricsKit.track("Purchase.started", payload: ["productId": product.id])AppMetricsKit.track("Purchase.completed", payload: ["productId": product.id], floatValue: price)AppMetricsKit.track("Purchase.failed", payload: ["reason": errorCode])AppMetricsKit.track("Subscription.trialStarted", payload: ["productId": product.id])AppMetricsKit.track("Subscription.renewed", payload: ["productId": product.id], floatValue: renewalPrice)AppMetricsKit.track("Subscription.refunded", payload: ["productId": product.id], floatValue: -refundAmount)Google Play Billing helper events
Android apps should send the same purchase lifecycle events from BillingClient callbacks and server-side purchase verification.
// Google Play Billing helper mappingAppMetricsKit.track("Purchase.started", mapOf("productId" to productDetails.productId))AppMetricsKit.track("Purchase.completed", mapOf("productId" to purchase.products.first()), amount)AppMetricsKit.track("Purchase.failed", mapOf("billingResponse" to responseCode))AppMetricsKit.track("Subscription.trialStarted", mapOf("basePlanId" to basePlanId))AppMetricsKit.track("Subscription.renewed", mapOf("productId" to productId), renewalAmount)AppMetricsKit.track("Subscription.billingRetry", mapOf("productId" to productId))