RevenueCat sends webhooks in response to events that occur in your app. Here these event types are defined, as well as the data contained in each webhook.
# Event Types
Webhook Event Type | Description | App Store | Play Store | Amazon | Stripe | Promo |
`TEST ` | Test event issued through the RevenueCat dashboard. | ✅ | ✅ | ❌ | ❌ | ❌ |
`INITIAL_PURCHASE ` | A new subscription has been purchased. | ✅ | ✅ | ✅ | ✅ | ❌ |
`RENEWAL ` | An existing subscription has been renewed or a lapsed user has resubscribed. | ✅ | ✅ | ✅ | ✅ | ❌ |
`CANCELLATION ` | A subscription or non-renewing purchase has been cancelled or refunded. Note that in the event of refunds, a subscription's auto-renewal setting may still be active. See [cancellation reasons](🔗) for more details. | ✅ | ✅ | ✅ | ✅ | ✅ |
`UNCANCELLATION ` | A non-expired cancelled subscription has been re-enabled. | ✅ | ✅ | ✅ | ❌ | ❌ |
`NON_RENEWING_PURCHASE ` | A customer has made a purchase that will not auto-renew. | ✅ | ✅ | ✅ | ✅ | ✅ |
`SUBSCRIPTION_PAUSED ` | The subscription has set to be paused at the end of the period.
Please note: You should not revoke access when receiving a `SUBSCRIPTION_PAUSED ` event, but only when receiving an `EXPIRATION ` event (which will have the [expiration reason](🔗) `SUBSCRIPTION_PAUSED `) | ❌ | ✅ | ❌ | ❌ | ❌ |
`EXPIRATION ` | A subscription has expired and access should be removed. If you have [Platform Server Notifications](🔗) configured, this event will occur as soon as we are notified (within seconds to minutes) of the expiration. If you do not have notifications configured, delays may be approximately 1 hour. | ✅ | ✅ | ✅ | ✅ | ✅ |
`BILLING_ISSUE ` | There has been a problem trying to charge the subscriber. This does not mean the subscription has expired.
Can be safely ignored if listening to `CANCELLATION ` event + `cancel_reason=BILLING_ERROR `. | ✅ | ✅ | ✅ | ✅ | ❌ |
`PRODUCT_CHANGE ` | A **subscriber** has changed the product of their subscription. This does not mean the new subscription is in effect immediately. See [Managing Subscriptions](🔗) for more details on updates, downgrades, and crossgrades. | ✅ | ✅ | ❌ | ✅ | ❌ |
`TRANSFER ` | A transfer of transactions and entitlements was initiated between one App User ID(s) to another. | ✅ | ✅ | ✅ | ✅ | ❌ |
`SUBSCRIBER_ALIAS ` | **Deprecated**. A new `app_user_id ` has been registered for an existing subscriber. | | | | | |
# Events Format
Webhook events are serialized in JSON. The body of a `POST
` request to your server will contain the serialized event, as well as the API version.
# Common Fields
Field | Type | Description | Possible Values |
`type ` | String | [Type of the event](🔗). | `TEST `
`INITIAL_PURCHASE `
`NON_RENEWING_PURCHASE `
`RENEWAL `
`PRODUCT_CHANGE `
`CANCELLATION `
`BILLING_ISSUE `
`SUBSCRIBER_ALIAS `
`SUBSCRIPTION_PAUSED `
`UNCANCELLATION `
`TRANSFER ` |
`id ` | String | Unique identifier of the event. | |
`app_id ` | String | Unique identifier of the app the event is associated with. Corresponds to an app within a project. This value will soon be visible in the app's configuration page in project settings. | |
`event_timestamp_ms ` | Integer | The time that the event was generated. Does not necessarily coincide with when the action that triggered the event occurred (purchased, cancelled, etc). | |
`app_user_id ` | String | Last seen app user id of the subscriber. | |
`original_app_user_id ` | String | The first app user id used by the subscriber. | |
`aliases ` | Arrayk:parame | All app user ids ever used by the subscriber. | |
When looking up users from the webhook in your systems, make sure to search both the `
original_app_user_id
` and the `aliases
` array.
If we have to retry a webhook for any reason, the retry will have the same `
id
` and `event_timestamp_ms
` of the first attempt.
# Subscription Lifecycle Events Fields
Field | Type | Description | Possible Values |
`product_id ` | String | Product identifier of the subscription. Please note: For Google Play products set up in RevenueCat after February 2023, this identifier has the format `<subscription_id>:<base_plan_id> ` | |
`entitlement_ids ` | Arrayk:parame | Entitlement identifiers of the subscription. | It can be `NULL ` if the `product_id ` is not mapped to any entitlements. |
`entitlement_id ` | String | Deprecated. See `entitlement_ids `. | Deprecated. See `entitlement_ids `. |
`period_type ` | String | Period type of the transaction. | `TRIAL `, for free trials.
`INTRO `, for introductory pricing.
`NORMAL `, standard subscription.
`PROMOTIONAL `, for subscriptions granted through RevenueCat.
`PREPAID `,for Play Store prepaid transactions . |
`purchased_at_ms ` | Integer | Time when the transaction was purchased. Measured in milliseconds since Unix epoch | |
`grace_period_expiration_at_ms ` | Integer | **Only available for** `BILLING_ISSUE ` **events.**
The time that the grace period for the subscription would expire. Measured in milliseconds since Unix epoch. Use this field to determine if the user is currently in a grace period. | It can be `NULL ` if subscription does not have a grace period. |
`expiration_at_ms ` | Integer | Expiration of the transaction. Measured in milliseconds since Unix epoch. Use this field to determine if a subscription is still active. | It can be `NULL ` for non-subscription purchases. |
`auto_resume_at_ms ` | Integer | The time when an Android subscription would resume after being paused. Measured in milliseconds since Unix epoch.
** Only available for Play Store subscriptions and** `SUBSCRIPTION_PAUSED ` **events.** | |
`store ` | String | Store the subscription belongs to. | `AMAZON `
`APP_STORE `
`MAC_APP_STORE `
`PLAY_STORE `
`PROMOTIONAL `
`STRIPE ` |
`environment ` | String | Store environment. | `SANDBOX `
`PRODUCTION ` |
`is_trial_conversion ` | Boolean | **Only available for** `RENEWAL ` **events**.
Whether the previous transaction was a free trial or not. | `true ` or `false ` |
`cancel_reason ` | String | **Only available for** `CANCELLATION ` **events**.
See [Cancellation and Expiration Reasons](🔗). | `UNSUBSCRIBE `
`BILLING_ERROR `
`DEVELOPER_INITIATED `
`PRICE_INCREASE `
`CUSTOMER_SUPPORT `
`UNKNOWN ` |
`expiration_reason ` | String | **Only available for** `EXPIRATION ` **events**.
See [Cancellation and Expiration Reasons](🔗). | `UNSUBSCRIBE `
`BILLING_ERROR `
`DEVELOPER_INITIATED `
`PRICE_INCREASE `
`CUSTOMER_SUPPORT `
`UNKNOWN ` |
`new_product_id ` | String | Product identifier of the new product the subscriber has switched to. **Only available for App Store subscriptions and** `PRODUCT_CHANGE ` **events**. | |
`presented_offering_id ` | String | **Not available for apps using legacy entitlements.** The identifier for the offering that was presented to the user during their initial purchase. | Can be `NULL ` if the purchase was made using purchaseProduct instead of purchasePackage or if the purchase was made outside of your app or before you integrated RevenueCat. |
`price ` | Double | The USD price of the transaction. | Can be `NULL ` if the price is unknown, and `0 ` for free trials.
Can be negative for refunds. |
`currency ` | String | The ISO 4217 currency code that the product was purchased in. | `USD `, `CAD `, etc.
Can be `NULL ` if the currency is unknown. |
`price_in_purchased_currency ` | Double | The price of the transaction in the currency the product was purchased in. | Can be `NULL ` if the price is unknown, and `0 ` for free trials.
Can be negative for refunds. |
`tax_percentage ` | Double | The estimated percentage of the transaction price that was deducted for taxes (varies by country and store). | Can be `NULL ` if the tax percentage is unknown. |
`commission_percentage ` | Double | The estimated percentage of the transaction price that was deducted as a store commission / processing fee. | Can be `NULL ` if the commission percentage is unknown. |
`takehome_percentage ` | Double | DEPRECATED: The estimated percentage of the transaction price that will be paid out to developers after commissions, but before VAT and DST taxes are taken into account. We recommend using tax_percentage and commission_percentage to calculate proceeds instead. [Learn more here](🔗). | |
`subscriber_attributes ` | Map of attribute names to attribute objects. For more details see the [subscriber attributes guide](🔗). | | |
`transaction_id ` | String | Transaction identifier from Apple/Amazon/Google/Stripe. | |
`original_transaction_id ` | String | `transaction_id ` of the original transaction in the subscription from Apple/Amazon/Google/Stripe. | |
`is_family_share ` | Boolean | Indicates if the user made this purchase or if it was shared to them via [Family Sharing](🔗). | `true ` or `false `
Always false for non-Apple purchases. |
`transferred_from ` | Arrayk:parame | **This fields is only available when `type ` is set to `TRANSFER `.**
App User ID(s) that transactions and entitlements are being taken from, and granted to `transferred_to `. | |
`transferred_to ` | Arrayk:parame | **This field is only available when `type ` is set to `TRANSFER `.**
App User ID(s) that are receiving the transactions and entitlements taken from `transferred_from `. | |
`country_code ` | String | The ISO 3166 country code that the product was purchased in. The two-letter country code (e.g., US, GB, CA) of the app user's location (this country code is derived from the last seen request from the SDK for the subscriber.) | `US `, `CA `, etc. |
`offer_code ` | String | **This field is not available when `type ` is set to `SUBSCRIBER_ALIAS ` or `TRANSFER `.**
The offer code that the customer used to redeem the transaction.
Available for App Store and Play Store. For App Store this property corresponds to the [`offer_code_ref_name `](🔗). For Play Store this corresponds to the [`promotionCode `](🔗). | Can be null if no offer code was used for this product. |
To get the RevenueCat event `
id
` from a Subscription Lifecycle webhook, simply make an API call to our GET `/subscribers
`endpoint with the `app_user_id
` after receiving the webhook and look for the latest purchase in the [subscription](🔗)/[non-subscription](🔗) object.
Determine trial and subscription duration
To get a trial or subscription's duration from a webhook, you can subtract purchased_at_ms from expiration_at_ms and you will get the duration of the trial in milliseconds.
# Cancellation and Expiration Reasons
Reason | Description | App Store | Play Store | Amazon | Web | Promo |
`UNSUBSCRIBE ` | Subscriber cancelled voluntarily. **This event fires when a user unsubscribes, not when the subscription expires.** | ✅ | ✅ | ✅ | ✅ | ❌ |
`BILLING_ERROR ` | Apple, Amazon, or Google could not charge the subscriber using their payment method.
The `CANCELLATION ` event with cancellation reason `BILLING_ERROR ` is fired as soon as the billing issue has been detected. The `EXPIRATION ` event with expiration reason `BILLING_ERROR ` is fired if the grace period (if set up) has ended without recovering the payment, and the customer should lose access to the subscription. | ✅ | ✅ | ✅ | ❌ | ❌ |
`DEVELOPER_INITIATED ` | Developer cancelled the subscription. | ✅ | ✅ | ❌ | ❌ | ✅ |
`PRICE_INCREASE ` | Subscriber did not agree to a price increase. | ✅ | ❌ | ❌ | ❌ | ❌ |
`CUSTOMER_SUPPORT ` | Customer received a refund from Apple support, a Play Store subscription was refunded through RevenueCat, an Amazon subscription was refunded through Amazon support, or a web subscription was refunded. Note that this does not mean that a subscription's autorenewal preference has been deactivated since refunds can be given without cancelling a subscription. You should check the current subscription status to check if the subscription is still active. | ✅ | ✅ | ✅ | ✅ | ❌ |
`UNKNOWN ` | Apple did not provide the reason of the cancellation. | ✅ | ❌ | ❌ | ❌ | ❌ |
`SUBSCRIPTION_PAUSED ` | The subscription expired because it was paused (only `EXPIRATION ` event) | ❌ | ✅ | ❌ | ❌ | ❌ |