Skip to main content

Overview

Flex integrates with RevenueCat to help you manage subscriptions seamlessly across platforms. This guide walks you through connecting your RevenueCat account to Flex, passing a stable customer identifier, saving the integration, and understanding how events map between systems.

Installation Guide

1

Add RevenueCat in Flex

Log in to your Flex Dashboard.Go to Integrations → RevenueCat.Enter your RevenueCat App ID and API Key.
  • You can find these in your RevenueCat project under App Settings. Flex Dashboard → Integrations page with RevenueCat selected
2

Configure in RevenueCat

In your RevenueCat project, open the Apps section.Click + Add and select Other payment provider.Copy your App ID and Secret API Key.Paste these values into the Flex Dashboard RevenueCat fields from Step 1.RevenueCat “Apps” page showing Other Payment Provider selectedRevenueCat “Apps” page showing Other Payment Provider selected
If you don’t see “Other payment provider (External source)” in your RevenueCat Apps page, that option may not be enabled for your project.Please contact RevenueCat Support and ask them to enable the External payment provider integration for your project.Include:
  • Your Project ID / link
  • The App name
  • That you want to connect Flex as the external payment provider
3

Pass a Custom User Identifier to RevenueCat

If you want RevenueCat to reference your own user ID (or a Flex user ID), include it in the checkout session metadata as REVENUE_CAT_CUSTOMER_ID.
{
  "checkout_session": {
    "line_items": [
      {
        "price": "fprice_01k2j2zfnbrs5e0r4c8wqphv29",
        "quantity": 1
      }
    ],
    "success_url": "https://example.com/thank-you?success=true",
    "mode": "subscription",
    "cancel_url": "https://example.com/thank-you?canceled=true",
    "shipping_address_collection": false,
    "defaults": {
      "first_name": "Varsha",
      "last_name": "Parthay",
      "email": "rclivedemo1@gmail.com"
    },
    "metadata": {
      "REVENUE_CAT_CUSTOMER_ID": "1234567890"
    }
  }
}
4

Save and Sync

Click Save in the Flex Dashboard integration page.Flex will begin sending subscription events to RevenueCat so customer status stays in sync.
Tip: If you have existing subscriptions, run an initial sync (or perform a backfill) so historical subscriptions are recognized in RevenueCat.

How Flex Integrates with RevenueCat

There are two ways subscriptions can flow into RevenueCat:

Native Stripe Integration

Stripe sends webhooks directly to RC. RC processes them and generates events with store: "STRIPE".

Flex External Purchases API

Flex processes payments via Stripe, then syncs subscription state to RC via the External Purchases API (POST /v1/receipts/external). RC generates events with store: "EXTERNAL".
For Flex-managed subscriptions, all events come through the External Purchases API path. The subscription data is accurate and revenue is correctly tracked — but certain webhook fields have different values than a native Stripe integration would produce.
If you’re migrating from a native Stripe → RevenueCat integration, be aware of these key differences:
  • store is "EXTERNAL", not "STRIPE" — update any webhook consumers that filter by store
  • Refund events have price: 0.0, never negative — use the RC Subscriber API or Flex API for refund amounts
  • expiration_reason is always null — use cancel_reason on preceding CANCELLATION events instead
  • Prorated refunds are invisible in webhooks — no CUSTOMER_SUPPORT event is generated; use Flex API to detect partial refunds
  • IDs use Flex format (fprod_, fsub_, fcus_) instead of Stripe format (prod_, si_, cus_) — treat all IDs as opaque strings
These differences are inherent to the RevenueCat External Purchases API and have been confirmed with the RevenueCat team (March 2026). Revenue is always accurately tracked inside RevenueCat — these differences only affect webhook payload fields.

Subscription Lifecycle Events

Flex syncs the following RevenueCat event types. This is the complete list — every event type that Flex sends to RC:
Flex Internal EventRC Webhook Event TypeDescriptionIncludes Payment?
TrialPurchaseINITIAL_PURCHASEFree trial started ($0 invoice). Subscription created with status: trialing.No
TrialConversionINITIAL_PURCHASETrial converted to paid subscription. First real payment processed.Yes (positive amount)
RenewalRENEWALSubscription renewed at start of a new billing period.Yes (positive amount)
BillingSucceedsRENEWALPayment recovered after a past_due period. Same logic as Renewal.Yes (positive amount)
BillingIssueBILLING_ISSUEPayment failed during renewal. Subscription enters grace period.No
ExpirationEXPIRATIONSubscription access ended. Triggered by Stripe customer.subscription.deleted.No
CancelAtPeriodEndCANCELLATIONSubscription set to not renew at end of current period. Still active.No
UndoCancelAtPeriodEndUNCANCELLATIONCancel-at-period-end reversed. Subscription will renew again.No
RefundCANCELLATIONRefund issued. Sends expired status + negative payment to adjust revenue.Yes (negative amount)
ProductChange (upgrades/downgrades) is not currently supported. All other subscription lifecycle events are fully implemented and synced in real-time.

Stripe Trigger Mapping

Each Flex RC sync event is triggered by a specific Stripe webhook event:
Stripe Webhook EventConditionFlex RC Event Type
invoice.payment_succeededamount_paid == 0 AND status == trialingTrialPurchase
invoice.payment_succeededstatus == trialing AND amount > 0TrialConversion
invoice.payment_succeededbilling_reason == subscription_createTrialConversion
invoice.payment_succeededstatus == active, billing cycleRenewal
invoice.payment_succeededstatus == past_dueBillingSucceeds
invoice.payment_failedAnyBillingIssue
customer.subscription.updatedcancel_at_period_end: false → trueCancelAtPeriodEnd
customer.subscription.updatedcancel_at_period_end: true → falseUndoCancelAtPeriodEnd
customer.subscription.deletedAnyExpiration
charge.refund.updatedrefund status == succeededRefund

Event Sequences by Scenario

#RC Event Typecancel_reasonpricegives_access
1INITIAL_PURCHASE$179.99true
#RC Event TypeTriggerpricestatus
1INITIAL_PURCHASETrial starts ($0 invoice)$0.00trialing
2INITIAL_PURCHASETrial converts (first payment)$179.99active
Both events appear as INITIAL_PURCHASE in RC webhooks. The first has price 0 and status trialing; the second has the actual price and status active.
#RC Event Typepricegives_access
1RENEWAL$179.99true
#RC Event TypeTriggerstatusgives_access
1BILLING_ISSUEPayment failsin_grace_periodtrue (grace period)
2RENEWALPayment retried and succeedsactivetrue
If payment never recovers, the subscription eventually receives an EXPIRATION event when Stripe deletes it.
#RC Event Typecancel_reasongives_accessauto_renewal_status
1CANCELLATIONUNSUBSCRIBEtruewill_not_renew
Subscription remains active until the current period ends. Access continues during this time.
#RC Event Typegives_accessauto_renewal_status
1UNCANCELLATIONtruewill_renew
#RC Event Typecancel_reason / expiration_reasonpricegives_access
1CANCELLATIONcancel_reason: UNSUBSCRIBE$0.00true
2EXPIRATIONexpiration_reason: null$0.00false
3CANCELLATIONcancel_reason: CUSTOMER_SUPPORT$0.00false
Event #3 is the refund signal. In a native Stripe integration, price would be -179.99.WiththeExternalAPI,thepriceisalways179.99. With the External API, the price is always `0.00`. Revenue IS correctly adjusted inside RC — query the Subscriber API to confirm net revenue.
#RC Event Typecancel_reason / expiration_reasonpricegives_access
1EXPIRATIONexpiration_reason: null$0.00false
2CANCELLATIONcancel_reason: UNSUBSCRIBE$0.00true
No CANCELLATION (CUSTOMER_SUPPORT) event is generated for prorated refunds. The refund is invisible in webhooks. Revenue is adjusted internally in RC. Use the Flex API or RC Subscriber API for refund details.
#RC Event Typeexpiration_reasongives_access
1EXPIRATIONnullfalse
This fires when the billing period ends after a CancelAtPeriodEnd was set.

Field Reference

Every RC webhook event from Flex will have these field values:
FieldValueNotes
store"EXTERNAL"Always EXTERNAL for Flex-managed subscriptions
environment"SANDBOX" or "PRODUCTION"Matches the Flex partner’s test_mode setting
product_idfprod_xxxFlex product ID (not a Stripe product ID)
transaction_idfsub_xxxFlex subscription ID
original_transaction_idfsub_xxxSame as transaction_id
app_user_idfcus_xxxFlex customer ID
currencyUSDISO 4217 currency code
price0.0 or positive amountNever negative — see Refund Handling
price_in_purchased_currencySame as priceAlways matches price
commission_percentage0.0Flex handles its own payment processing
takehome_percentage1.0Flex handles its own payment processing
tax_percentage0.0Tax reported as zero
country_codenullNot available via External API
entitlement_idsnull or populatedDepends on RC project entitlement configuration
renewal_number1Always 1 — External API limitation
is_family_sharefalseNot applicable
period_type"NORMAL"Always NORMAL
subscriber_attributes{}Empty unless custom attributes configured
metadatanullNot populated
expiration_reasonnullNever populated for External store
cancel_reason"UNSUBSCRIBE" or "CUSTOMER_SUPPORT"UNSUBSCRIBE = user-initiated cancel. CUSTOMER_SUPPORT = refund.

Key Differences from Stripe-Native Integration

If your webhook consumer was originally built for a native Stripe → RevenueCat integration, the following differences apply when receiving events from Flex. These are inherent to the RevenueCat External Purchases API and have been confirmed with the RevenueCat team (March 2026).
FieldStripe-Native ValueFlex External ValueWhyImpact
store"STRIPE""EXTERNAL"RC sets this based on app typeMust accept both values
price (refund)Negative (e.g. -179.99)Always 0.0External API does not expose payment amounts in webhook price fieldCannot detect refund amount from webhook
price_in_purchased_currency (refund)NegativeAlways 0.0Same as aboveSame as above
expiration_reason"UNSUBSCRIBE"nullExternal API does not set this fieldAccept null as valid
commission_%~0.03 (Stripe fee)0.0Flex handles its own payment processingInformational only
takehome_%~0.971.0Derived from zero commissionInformational only
product_idprod_xxx (Stripe)fprod_xxx (Flex)Flex uses its own product IDsTreat as opaque string
transaction_idsi_xxx (Stripe sub item)fsub_xxx (Flex sub)Flex uses its own subscription IDsTreat as opaque string
app_user_idcus_xxx (Stripe customer)fcus_xxx (Flex customer)Flex uses its own customer IDsTreat as opaque string
renewal_numberIncrements each periodAlways 1External API does not track renewal countTrack externally if needed
These differences are not bugs. They are confirmed RevenueCat platform behavior for the External Purchases API. Flex sends all available data correctly; the External API simply exposes different fields than native integrations.

Refund Handling

Refund visibility in RC webhooks depends on the refund type and flow:
ScenarioRC Webhook Eventcancel_reasonpriceRevenue Adjusted in RC?
Full refund (immediate cancel + refund)CANCELLATIONCUSTOMER_SUPPORT0.0 (not negative)Yes — gross = $0.00
Prorated refundNo specific refund eventN/AN/AYes — gross = net amount
Custom refund amountNo specific refund eventN/AN/AYes — gross = net amount
Cancel at period end then later refundMay not generate CUSTOMER_SUPPORTN/AN/AYes — always adjusted

How to detect and measure refunds

MethodHowDetects Full Refunds?Detects Prorated Refunds?Gets Refund Amount?
RC Webhookcancel_reason == "CUSTOMER_SUPPORT"Yes (2-step flow only)NoNo (price is 0.0)
RC Subscriber APIGET /v1/subscribers/{id} → check total_revenue_in_usdYesYesYes (net revenue)
Flex API / WebhooksFlex refund events with amounts, types, timestampsYesYesYes (exact amount)
Recommended: Use Flex webhooks for refund tracking (exact amounts and types) and RC webhooks for subscription lifecycle (active, canceled, expired, billing).

Recommendations for Webhook Consumers

Handling the store field

Accept events where store is either "STRIPE" or "EXTERNAL":
if event.store in ["STRIPE", "EXTERNAL"]:
    process_event(event)

Detecting subscription state changes

Use the event type and cancel_reason to determine subscription state:
ConditionMeaning
type == INITIAL_PURCHASENew subscription or trial conversion
type == RENEWALSuccessful renewal payment or billing recovery
type == CANCELLATION AND cancel_reason == UNSUBSCRIBEUser canceled (won’t renew, still active)
type == CANCELLATION AND cancel_reason == CUSTOMER_SUPPORTRefund issued (full refund only)
type == UNCANCELLATIONCancel reversed, will renew again
type == EXPIRATIONAccess revoked, subscription ended
type == BILLING_ISSUEPayment failed, in grace period

Handling refunds

1

Detect refund occurrence

Check for cancel_reason == "CUSTOMER_SUPPORT" on CANCELLATION events.
2

Do NOT rely on negative price

The price will always be 0.0 for External store events.
3

Get refund amounts

Use the Flex API or RC Subscriber API for exact refund amounts.
4

Handle prorated/custom refunds

No RC webhook event is generated for these — use Flex webhooks instead.

Handling expiration events

Accept expiration_reason: null. The External store does not populate this field. To determine why a subscription expired, look at the preceding CANCELLATION event’s cancel_reason.

ID formats

Treat all IDs as opaque strings. Do not parse, validate format, or assume a prefix:
ID FieldStripe-Native FormatFlex Format
product_idprod_xxxfprod_xxx
transaction_idsi_xxxfsub_xxx
original_transaction_idsi_xxxfsub_xxx
app_user_idcus_xxxfcus_xxx

Example Payloads

All payloads below are from real webhook captures in the RevenueCat sandbox.
{
  "api_version": "1.0",
  "event": {
    "type": "INITIAL_PURCHASE",
    "store": "EXTERNAL",
    "environment": "SANDBOX",
    "product_id": "fprod_xxx",
    "transaction_id": "fsub_xxx",
    "original_transaction_id": "fsub_xxx",
    "app_user_id": "fcus_xxx",
    "currency": "USD",
    "price": 0.0,
    "price_in_purchased_currency": 0.0,
    "period_type": "NORMAL",
    "renewal_number": 1,
    "is_family_share": false,
    "commission_percentage": 0.0,
    "takehome_percentage": 1.0,
    "tax_percentage": 0.0,
    "country_code": null,
    "entitlement_ids": null,
    "expiration_reason": null,
    "cancel_reason": null,
    "subscriber_attributes": {}
  }
}

FAQ

Flex uses RevenueCat’s External Purchases API to sync subscription data. RC automatically sets store to "EXTERNAL" for all events processed through this API. This is expected behavior, not a misconfiguration.
The External Purchases API does not expose payment amounts in the webhook price field. Flex sends the correct negative payment to RC, and revenue IS adjusted correctly inside RC. But the webhook event’s price field is derived from the subscription record, not the payment. This is a confirmed RevenueCat platform limitation (verified March 2026).
Three options:
  1. RC Subscriber API: GET /v1/subscribers/{app_user_id} — check total_revenue_in_usd
  2. Flex Webhooks: Use Flex’s own webhook events, which include exact refund amounts and types
  3. Flex API: Query the Flex API for subscription/refund details
RevenueCat only generates a CANCELLATION (CUSTOMER_SUPPORT) event for full refunds processed through the External API. Prorated and custom refunds adjust revenue internally but do not trigger a distinct webhook event. Use the Flex API or RC Subscriber API to detect partial refunds.
The External Purchases API does not have a mechanism to set the expiration reason. This field is only populated for native integrations (Apple, Google, Stripe). To determine why a subscription expired, check the cancel_reason on the preceding CANCELLATION event.
Yes. Revenue (including refund adjustments) is always accurate in RC. The differences are only in webhook event payloads, not in RC’s internal data. You can always query GET /v1/subscribers/{id} to get accurate revenue figures.
Yes. Flex syncs both TrialPurchase (trial start, $0 invoice) and TrialConversion (first real payment after trial). Both appear as INITIAL_PURCHASE in RC webhooks — differentiate by checking price (0.0 for trial start, positive for conversion).
Yes. When cancel_at_period_end is reversed, Flex sends an UndoCancelAtPeriodEnd event which appears as UNCANCELLATION in RC webhooks.
Yes. BillingIssue is sent when payment fails (status: in_grace_period, gives_access: true). BillingSucceeds is sent when payment recovers (same logic as Renewal).
These are inherent to the RevenueCat External Purchases API as of March 2026. If RC updates the External API to support additional fields, Flex will adopt them. This document will be updated accordingly.