Superwall

Integrations

Use webhooks to get real-time notifications about your app's subscription and payment events. Integrate Superwall with other services.

The integrations page is where you can manage your webhooks and other integrations with Superwall:

Webhooks

Superwall sends webhooks to notify your application about important subscription and payment events in real-time. These webhooks are designed to closely match App Store and other revenue provider events, minimizing migration difficulty.

Important Design Principle: Webhook events are structured so that summing proceeds or price across all events (without filtering) accurately represents total revenue net of refunds. To calculate gross revenue, filter out events with negative proceeds.

Webhook Payload Structure

Every webhook sent by Superwall contains the following structure:

{
  "object": "event",
  "type": "renewal",
  "projectId": 3827,
  "applicationId": 1,
  "timestamp": 1754067715103,
  "data": {
    "id": "42fc6339-dc28-470b-a0fa-0d13c92d8b61:renewal",
    "name": "renewal",
    "cancelReason": null,
    "exchangeRate": 1.0,
    "isSmallBusiness": false,
    "periodType": "NORMAL",
    "countryCode": "US",
    "price": 9.99,
    "proceeds": 6.99,
    "priceInPurchasedCurrency": 9.99,
    "taxPercentage": 0,
    "commissionPercentage": 0.3,
    "takehomePercentage": 0.7,
    "offerCode": null,
    "isFamilyShare": false,
    "expirationAt": 1756659704000,
    "transactionId": "700002054157982",
    "originalTransactionId": "700002050981465",
    "originalAppUserId": "$SuperwallAlias:7152E89E-60A6-4B2E-9C67-D7ED8F5BE372",
    "store": "APP_STORE",
    "purchasedAt": 1754067704000,
    "currencyCode": "USD",
    "productId": "com.example.premium.monthly",
    "environment": "PRODUCTION",
    "isTrialConversion": false,
    "newProductId": null,
    "bundleId": "com.example.app",
    "ts": 1754067710106
  }
}

Webhook Payload Fields

FieldTypeDescription
objectstringAlways "event"
typestringThe event type (e.g., "initial_purchase", "renewal", "cancellation")
projectIdnumberYour Superwall project ID
applicationIdnumberYour Superwall application ID
timestampnumberEvent timestamp in milliseconds since epoch
dataobjectEvent-specific data (see below)

Event Data Object

The data field contains detailed information about the subscription or payment event:

Event Data Fields

FieldTypeDescription
idstringUnique identifier for this event
namestringEvent name (see Event Names)
cancelReasonstring or nullReason for cancellation (see Cancel Reasons)
exchangeRatenumberExchange rate used to convert to USD
isSmallBusinessbooleanSmall business program participant
periodTypestringPeriod type: TRIAL, INTRO, or NORMAL
countryCodestringISO country code (e.g., "US")
pricenumberTransaction price in USD (negative for refunds)
proceedsnumberNet proceeds in USD after taxes and fees
priceInPurchasedCurrencynumberPrice in original currency
taxPercentagenumber or nullTax percentage applied
commissionPercentagenumberStore commission percentage
takehomePercentagenumberYour percentage after commission
offerCodestring or nullPromotional offer code used
isFamilySharebooleanFamily sharing purchase
expirationAtnumber or nullExpiration timestamp (milliseconds)
transactionIdstringCurrent transaction ID
originalTransactionIdstringOriginal transaction ID (subscription ID)
originalAppUserIdstring or nullOriginal app user ID (see details)
storestringStore: APP_STORE, PLAY_STORE, STRIPE, or PADDLE (see note below)
purchasedAtnumberPurchase timestamp (milliseconds)
currencyCodestringISO currency code for priceInPurchasedCurrency
productIdstringProduct identifier
environmentstringPRODUCTION or SANDBOX
isTrialConversionbooleanTrial to paid conversion
newProductIdstring or nullNew product ID (for product changes)
bundleIdstringApp bundle identifier
tsnumberEvent timestamp (milliseconds)
expirationReasonstring (optional)Reason for expiration (see Cancel Reasons)
checkoutContextobject (optional)Stripe-specific checkout context

Note on Store field: iOS and Android apps can receive events from any payment provider. For example, an iOS app can receive STRIPE or PADDLE events when users purchase through Superwall's App2Web features, which allow web-based checkout flows within mobile apps. The store field indicates where the payment was processed, not which platform the app runs on.

Event Names

Event NameValueDescription
Initial Purchaseinitial_purchaseFirst-time subscription or purchase
RenewalrenewalSubscription renewal
CancellationcancellationSubscription cancelled
UncancellationuncancellationSubscription reactivated
ExpirationexpirationSubscription expired
Billing Issuebilling_issuePayment processing failed
Product Changeproduct_changeUser changed subscription tier
Subscription Pausedsubscription_pausedSubscription temporarily paused
Non-Renewing Purchasenon_renewing_purchaseOne-time purchase
TesttestTest event for webhook verification

Period Types

Period TypeValueDescription
TrialTRIALFree trial period
IntroINTROIntroductory offer period (discounted rate)
NormalNORMALRegular subscription period (full price)

Stores

StoreValueDescription
App StoreAPP_STOREApple App Store
Play StorePLAY_STOREGoogle Play Store
StripeSTRIPEStripe payments
PaddlePADDLEPaddle payments (coming soon)

Environments

EnvironmentValueDescription
ProductionPRODUCTIONLive production transactions
SandboxSANDBOXTest transactions (not real money)

Cancel/Expiration Reasons

ReasonValueDescription
Billing ErrorBILLING_ERRORPayment method failed
Customer SupportCUSTOMER_SUPPORTCancelled via support
UnsubscribeUNSUBSCRIBEUser-initiated cancellation
Price IncreasePRICE_INCREASECancelled due to price change
Developer InitiatedDEVELOPER_INITIATEDCancelled programmatically
UnknownUNKNOWNReason not specified

Common Use Cases

Detecting Trial Starts

if (
  event.data.periodType === "TRIAL" &&
  event.data.name === "initial_purchase"
) {
  // New trial started
}

Detecting Trial Conversions

if (
  event.data.name === "renewal" &&
  (event.data.isTrialConversion ||
    event.data.periodType === "TRIAL" ||
    event.data.periodType === "INTRO")
) {
  // Trial or intro offer converted to paid subscription
}

Detecting Trial Cancellations

if (event.data.periodType === "TRIAL" && event.data.name === "cancellation") {
  // Trial cancelled
}

Detecting Trial Uncancellations (Reactivations)

if (event.data.periodType === "TRIAL" && event.data.name === "uncancellation") {
  // Trial reactivated after cancellation
}

Detecting Trial Expirations

if (event.data.periodType === "TRIAL" && event.data.name === "expiration") {
  // Trial expired
}

Detecting Intro Offer Starts

if (
  event.data.periodType === "INTRO" &&
  event.data.name === "initial_purchase"
) {
  // Intro offer started
}

Detecting Intro Offer Cancellations

if (event.data.periodType === "INTRO" && event.data.name === "cancellation") {
  // Intro offer cancelled
}

Detecting Intro Offer Uncancellations

if (event.data.periodType === "INTRO" && event.data.name === "uncancellation") {
  // Intro offer reactivated
}

Detecting Intro Offer Expirations

if (event.data.periodType === "INTRO" && event.data.name === "expiration") {
  // Intro offer expired
}

Detecting Intro Offer Conversions

if (event.data.periodType === "INTRO" && event.data.name === "renewal") {
  // Intro offer converted to regular subscription
}

Detecting Subscription Starts

if (
  event.data.periodType === "NORMAL" &&
  event.data.name === "initial_purchase"
) {
  // New paid subscription started
}

Detecting Renewals

if (
  event.data.name === "renewal" &&
  event.data.periodType === "NORMAL" &&
  !event.data.isTrialConversion
) {
  // Regular subscription renewal
}

Detecting Refunds

if (event.data.price < 0) {
  // Refund processed
  const refundAmount = Math.abs(event.data.price);
}

Detecting Cancellations

if (event.data.name === "cancellation") {
  // Subscription cancelled
  // Check cancelReason for details
  const reason = event.data.cancelReason;
}

Detecting Subscription Expirations

if (event.data.name === "expiration") {
  // Subscription expired
  // Check expirationReason for details
}

Detecting Billing Issues

if (event.data.name === "billing_issue") {
  // Payment failed - subscription at risk
}

Detecting Subscription Pauses

if (event.data.name === "subscription_paused") {
  // Subscription has been paused
}

Detecting Product Changes

if (event.data.name === "product_change") {
  // User changed subscription plan
  const oldProduct = event.data.productId;
  const newProduct = event.data.newProductId;
}

Detecting Subscription Reactivations

if (event.data.name === "uncancellation") {
  // Previously cancelled subscription was reactivated
}

Detecting Non-Renewing Purchases

if (event.data.name === "non_renewing_purchase") {
  // One-time purchase completed
}

Detecting Revenue Events

if (event.data.price !== 0 || event.data.name === "non_renewing_purchase") {
  // This event involves revenue (positive or negative)
}

Detecting Test Events

if (event.data.name === "test") {
  // Test webhook for endpoint verification
}

Revenue Calculation

Total Net Revenue (Including Refunds)

// Sum all proceeds - automatically accounts for refunds
const netRevenue = events.reduce((sum, event) => sum + event.data.proceeds, 0);

Gross Revenue (Excluding Refunds)

// Only sum positive proceeds
const grossRevenue = events.reduce(
  (sum, event) => (event.data.proceeds > 0 ? sum + event.data.proceeds : sum),
  0
);

Refund Total

// Sum negative proceeds
const refunds = events.reduce(
  (sum, event) =>
    event.data.proceeds < 0 ? sum + Math.abs(event.data.proceeds) : sum,
  0
);

Revenue by Product

const revenueByProduct = {};
events.forEach((event) => {
  const productId = event.data.productId;
  if (!revenueByProduct[productId]) {
    revenueByProduct[productId] = 0;
  }
  revenueByProduct[productId] += event.data.proceeds;
});

Webhook Security

All webhooks are signed using HMAC-SHA256. Verify the signature before processing:

  1. Extract the signature from the X-Webhook-Signature header
  2. Compute HMAC-SHA256 of the raw request body using your webhook secret
  3. Compare the computed signature with the received signature

Testing Webhooks

Test events can be identified by:

  • event.data.name === "test"
  • These events have minimal data and are used for webhook endpoint verification

Best Practices

  1. Always verify webhook signatures to ensure authenticity
  2. Handle duplicate events - Use event.id for idempotency
  3. Process webhooks asynchronously - Return 200 immediately, then process
  4. Store raw webhook data for debugging and reconciliation
  5. Handle all event types - Even if you don't process them immediately
  6. Monitor webhook failures - Implement retry logic for critical events
  7. Use timestamps - All timestamps are in milliseconds since epoch

Store-Specific Behaviors

Commission Rates by Store

APP_STORE:

  • Standard rate: 30%
  • Small Business Program rate: 15% (for eligible developers)
  • Clean, predictable commission structure

PLAY_STORE:

  • Variable rates from 11.8% to 15%
  • Most common rate: 15%
  • Rates can vary based on region and other factors

STRIPE:

  • Variable rates from 0% to ~7.2%
  • Generally lower than mobile app stores
  • Depends on Stripe pricing plan and transaction type

Price = 0 Events

Events commonly have price = 0 for non-revenue scenarios:

  • billing_issue - Payment failed, no money collected
  • cancellation - Subscription cancelled, no charge
  • expiration - Subscription expired, no charge
  • uncancellation - Reactivation, no immediate charge
  • product_change - Plan change notification
  • subscription_paused - Pause event, no charge

Revenue events (initial_purchase, renewal, non_renewing_purchase) typically have non-zero prices unless:

  • Family sharing scenario (some cases)
  • Special promotional offers
  • Test transactions

Cancel/Expiration Reasons by Store

APP_STORE:

  • CUSTOMER_SUPPORT - Cancelled via Apple support
  • UNSUBSCRIBE - User-initiated cancellation
  • BILLING_ERROR - Payment failure

PLAY_STORE:

  • All APP_STORE reasons plus:
  • UNKNOWN - Reason not specified or unavailable

STRIPE:

  • UNKNOWN - Stripe typically doesn't provide detailed cancellation reasons

Trial Conversions

Expected behavior: isTrialConversion should only be true for renewal events

Offer Codes Support

StoreSupportNotes
APP_STORE✅ SupportedRarely used (1.3% of events), typically for win-back campaigns
PLAY_STORE✅ SupportedHeavily used (72.1% of events), complex promotional system
STRIPE❌ Not supportedOffer codes not available in webhook data
PADDLE🔜 Coming soonSupport planned

Environment Field

All stores support both PRODUCTION and SANDBOX environments:

  • PRODUCTION: Live, real-money transactions
  • SANDBOX: Test transactions (TestFlight on iOS, test mode on Stripe, test purchases on Play Store)

The environment field helps you filter out test transactions from production analytics.

Store Event Compatibility Matrix

Not all events are available for all stores. This table shows which events you can expect from each store based on real webhook data:

Event Support by Store

Event NameAPP_STOREPLAY_STORESTRIPEPADDLE
billing_issue🔜
cancellation🔜
expiration🔜
initial_purchase🔜
non_renewing_purchase🔜
product_change🔜
renewal🔜
subscription_paused🔜
uncancellation🔜

✅ = Supported | ❌ = Not supported | 🔜 = Coming soon

Period Type Availability by Store

Different stores support different period types for events:

APP_STORE

  • Supports all period types (TRIAL, INTRO, NORMAL) for most events
  • non_renewing_purchase only occurs with NORMAL period type

PLAY_STORE

  • Supports all period types (TRIAL, INTRO, NORMAL) for most events
  • renewal only occurs with NORMAL period type
  • subscription_paused only occurs with INTRO and NORMAL period types
  • Unique: Only store that supports subscription_paused events

STRIPE

  • Limited period type support compared to mobile app stores
  • No INTRO period type support observed
  • expiration and renewal only occur with NORMAL period type
  • Does not support non_renewing_purchase or product_change events

PADDLE

  • Coming soon - full support planned!

Store-Specific Considerations

Universal Events (available across APP_STORE, PLAY_STORE, and STRIPE):

  • billing_issue
  • cancellation
  • expiration
  • initial_purchase
  • renewal
  • uncancellation

Store-Specific Events:

  • subscription_paused - Only available from PLAY_STORE
  • non_renewing_purchase - Not available from STRIPE
  • product_change - Not available from STRIPE

Understanding originalAppUserId

The originalAppUserId field represents the first app user ID associated with a subscription. This field has specific behavior depending on your integration:

Key Points:

  • What it represents: The first user ID we saw associated with this subscription (originalTransactionId)
  • Cross-account subscriptions: Since subscriptions are tied to Apple/Google accounts (not app accounts), users can create multiple accounts in your app while using the same subscription
  • We only store the first one: If a user creates multiple accounts, we only track the original user ID

When this field is populated:

  • iOS/App Store:
    • If your user ID has been sent to the stores on-device (via StoreKit)
    • If your user IDs are UUIDv4 format
    • This field will be consistently present for these cases
  • Stripe: Always populated (we create one for you if not provided)
  • Play Store: Depends on the integration and user tracking

When this field is null:

  • Legacy users: Users on old SDK versions
  • Pre-Superwall purchases: Users who purchased before integrating Superwall
  • No user ID sent: If user ID was never sent to the store

Understanding originalTransactionId

The originalTransactionId is Apple's terminology that acts like a subscription ID. For simplicity and consistency with iOS and other revenue tracking platforms, we use this nomenclature and populate it accordingly for all platforms (Play Store, Stripe, Paddle, etc.).

  • One per subscription group: Each user subscription gets one originalTransactionId
  • Persists across renewals: The same originalTransactionId is used for all renewals in that subscription
  • Multiple IDs per user: A single user can have multiple originalTransactionId if they:
    • Subscribe to products in different subscription groups
    • Let a subscription fully expire and re-subscribe later
  • Cross-platform consistency: While originally an Apple concept, we generate and maintain equivalent IDs for all payment providers to ensure consistent subscription tracking

Notes

  • Currency handling:

    • price and proceeds are always in USD
    • priceInPurchasedCurrency is in the currency specified by currencyCode
    • exchangeRate was used to convert from original currency to USD
  • Family Sharing (App Store only):

    • When isFamilyShare is true with price > 0: These are events for the family organizer who pays for the subscription (initial_purchase, renewal, non_renewing_purchase)
    • When isFamilyShare is true with price = 0: These are events for family members who use the shared subscription without paying (renewal, uncancellation, billing_issue, etc.)
  • Refunds: Negative values in price, proceeds, or priceInPurchasedCurrency indicate refunds

  • Transaction IDs:

    • transactionId: Unique ID for this specific transaction
    • originalTransactionId: Subscription ID (first transaction in the subscription group)
  • Commission and tax percentages help you understand the revenue breakdown

  • Timestamps:

    • timestamp (root level): When the webhook was created
    • ts (in data): When the actual event occurred
    • purchasedAt: When the transaction was originally purchased

    Integrations

    Currently, we support the following integrations:

    • Mixpanel: Track events and user properties in Mixpanel.
    • Slack: Send notifications to Slack channels.
    • Amplitude: Product analytics for your app.

    To set up any of these, click on them and fill in the required fields:

    Once you've done that, click the Enable button at the bottom right to save your changes.

Mixpanel Integration - Required Fields

The following fields are required to configure the Mixpanel integration:

Region *

  • Description: Data residency region for your Mixpanel project
  • Type: Dropdown selection
  • Required: Yes

Project Token *

  • Description: Your Mixpanel project token
  • Type: Text input
  • Required: Yes
  • Location: Mixpanel → Settings → Project Settings → Project Token

Total Spend Property *

  • Description: The name of the user property to track cumulative spend
  • Type: Text input
  • Required: Yes

Sales Reporting *

  • Description: Whether to report Proceeds after store taxes & fees or Revenue
  • Type: Dropdown selection
  • Required: Yes
  • Options:
    • Proceeds (after store taxes & fees)
    • Revenue

Optional Configuration

Sandbox Project Token

  • Description: Optional project token for sandbox events
  • Type: Text input
  • Required: No
  • Note: Leave blank to opt out of sandbox event tracking

Slack Integration - Required Fields

The following fields are required to configure the Slack integration:

Required Configuration

Webhook Url *

  • Description: Your Slack webhook URL for sending messages to a channel
  • Type: Text input
  • Required: Yes

Optional Configuration

Include Sandbox

  • Description: Whether to include sandbox events in Slack notifications
  • Type: Dropdown selection
  • Required: No

Event Type

  • Description: Type of events to send: revenue only or all lifecycle (includes trials, cancellations)
  • Type: Dropdown selection
  • Required: No
  • Options:
    • Revenue only
    • All lifecycle (includes trials, cancellations)

Amplitude Integration - Required Fields

The following fields are required to configure the Amplitude integration:

Required Configuration

Region *

  • Description: Data residency region for your Amplitude project
  • Type: Dropdown selection
  • Required: Yes

Api Key *

  • Description: Your Amplitude API key
  • Type: Text input
  • Required: Yes

Sales Reporting *

  • Description: Which revenue value to report in Amplitude
  • Type: Dropdown selection
  • Required: Yes

Optional Configuration

Sandbox Api Key

  • Description: Optional API key for sandbox events
  • Type: Text input
  • Required: No
  • Note: Leave blank to opt out of sandbox event tracking

How is this guide?

On this page

WebhooksWebhook Payload StructureWebhook Payload FieldsEvent Data ObjectEvent Data FieldsEvent NamesPeriod TypesStoresEnvironmentsCancel/Expiration ReasonsCommon Use CasesDetecting Trial StartsDetecting Trial ConversionsDetecting Trial CancellationsDetecting Trial Uncancellations (Reactivations)Detecting Trial ExpirationsDetecting Intro Offer StartsDetecting Intro Offer CancellationsDetecting Intro Offer UncancellationsDetecting Intro Offer ExpirationsDetecting Intro Offer ConversionsDetecting Subscription StartsDetecting RenewalsDetecting RefundsDetecting CancellationsDetecting Subscription ExpirationsDetecting Billing IssuesDetecting Subscription PausesDetecting Product ChangesDetecting Subscription ReactivationsDetecting Non-Renewing PurchasesDetecting Revenue EventsDetecting Test EventsRevenue CalculationTotal Net Revenue (Including Refunds)Gross Revenue (Excluding Refunds)Refund TotalRevenue by ProductWebhook SecurityTesting WebhooksBest PracticesStore-Specific BehaviorsCommission Rates by StorePrice = 0 EventsCancel/Expiration Reasons by StoreTrial ConversionsOffer Codes SupportEnvironment FieldStore Event Compatibility MatrixEvent Support by StorePeriod Type Availability by StoreAPP_STOREPLAY_STORESTRIPEPADDLEStore-Specific ConsiderationsUnderstanding originalAppUserIdKey Points:When this field is populated:When this field is null:Understanding originalTransactionIdNotesIntegrationsMixpanel Integration - Required FieldsRegion *Project Token *Total Spend Property *Sales Reporting *Optional ConfigurationSandbox Project TokenSlack Integration - Required FieldsRequired ConfigurationOptional ConfigurationAmplitude Integration - Required FieldsRequired ConfigurationOptional Configuration