Connect subscription providers to revenue analytics.
AppMetricsKit accepts authenticated 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>Configure provider authentication
Provider credentials are saved separately for each app. Generate a unique authorization value for RevenueCat or Adapty. For Superwall, copy its Svix signing secret. AppMetricsKit verifies every webhook request and protects the saved credentials.
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.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, and AppMetricsKit does not automatically merge them with SDK purchase events. Send each transaction from one source unless separate client-side and server-side records are intentional.
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. Start a retained Transaction.updates observer when the app launches, verify every transaction, and make entitlement delivery idempotent with a durable check keyed by transaction.id and lifecycle state. Classify revoked transactions as refunds, use transaction.reason to distinguish renewals, and convert transaction.price to Double when available. Finish a transaction only after entitlement delivery succeeds.
import AppMetricsKitimport Foundationimport StoreKit
// Call only after StoreKit verification and entitlement delivery.func trackVerifiedTransaction(_ transaction: Transaction) { let amount = transaction.price.map { NSDecimalNumber(decimal: $0).doubleValue } let payload: AppMetricsPayload = [ "productId": .string(transaction.productID) ]
if transaction.revocationDate != nil { AppMetricsKit.track( "Subscription.refunded", payload: payload, floatValue: amount.map { -abs($0) } ) } else if transaction.reason == .renewal { AppMetricsKit.track( "Subscription.renewed", payload: payload, floatValue: amount ) } else { AppMetricsKit.track( "Purchase.completed", payload: payload, floatValue: amount ) }}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))