Revenue integrations

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.

Code
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.

Code
PROVIDER_WEBHOOK_SYNC_SECRET=...PROVIDER_SECRET_ENCRYPTION_KEY=... # recommended 32+ characters, set in Convex

RevenueCat

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.refunded with 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.

Code
// 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.

Code
// 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))