Webhooks

Enhanced server-to-server notifications from webhooks

👍

Webhooks are available on the Starter plan or greater.

RevenueCat can send you notifications any time an event happens in your app. This is useful for subscription and purchase events, which will allow you to monitor state changes for your subscribers and react accordingly.

For example, you might want to remind subscribers of the benefits of your app when they decide to unsubscribe, or let them know when there are billing issues.

Registering Your Webhook URL

📘

One webhook per project in RevenueCat

Each of your projects has a webhook URL field you can set. This way you can decide which of your projects need server-side notifications, and where they should be sent.

  1. Navigate to your project in the RevenueCat dashboard and find the Integrations card in the left menu. Select + New
  1. Choose 'Webhooks' from the Integrations menu
  2. Enter the HTTPS URL of the endpoint that you want to receive your webhooks
  3. (Optional) Set authorization header that will be sent with each POST request

📘

Best Practices: Webhook authorization

We recommended setting an authorization header value via the RevenueCat dashboard. When set, RevenueCat will send this header in every request. Your server can use this to authenticate the webhooks from RevenueCat.

RevenueCat will send POST requests to your server, in which the body will be a JSON representation of the notification. Your server should return a 200 status code. Any other status code will be considered a failure by our backend. RevenueCat will retry later (up to 5 times) with an increasing delay (5, 10, 20, 40, and 80 minutes). After 5 retries, we will stop sending notifications.

If you're receiving a webhook it's important to respond quickly so you don't accidentally run over a timeout limit. We recommend that apps defer processing until after the response has been sent.

Webhook Events

Event Types

Webhook Event Type

Description

App Store

Play Store

Amazon

Web

Promo

TEST

Test event issued through the RevenueCat dashboard.

✅

✅

❌

❌

❌

INITIAL_PURCHASE

A new subscription has been purchased.

✅

✅

✅

✅

❌

NON_RENEWING_PURCHASE

A customer has made a purchase that will not auto-renew.

✅

✅

✅

✅

✅

RENEWAL

An existing subscription has been renewed. This may occur at the end of the current billing period or later if a lapsed user re-subscribes.

✅

✅

✅

✅

❌

PRODUCT_CHANGE

A subscriber has changed the product of their subscription.

✅

✅

❌

✅

❌

CANCELLATION

A subscription or non-renewing purchase has been cancelled. See cancellation reasons for more details.

✅

✅

✅

✅

✅

UNCANCELLATION

Auto-renew status has been re-enabled for a subscription.

✅

✅

✅

❌

❌

BILLING_ISSUE

There has been a problem trying to charge the subscriber. This does not mean the subscription has expired.

✅

✅

✅

✅

❌

SUBSCRIBER_ALIAS

Deprecated. A new app_user_id has been registered for an existing subscriber.

SUBSCRIPTION_PAUSED

A subscription has been paused.

❌

✅

❌

❌

❌

TRANSFER

A transfer of transactions and entitlements was initiated between one App User ID(s) to another.

✅

✅

✅

✅

❌

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.

✅

✅

✅

✅

✅

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.

{
  "api_version": "1.0",
  "event": {
    "aliases": [
      "yourCustomerAliasedID",
      "yourCustomerAliasedID"
    ],
    "app_id": "yourAppID",
    "app_user_id": "yourCustomerAppUserID",
    "country_code": "US",
    "currency": "USD",
    "entitlement_id": "pro_cat",
    "entitlement_ids": [
      "pro_cat"
    ],
    "environment": "PRODUCTION",
    "event_timestamp_ms": 1591121855319,
    "expiration_at_ms": 1591726653000,
    "id": "UniqueIdentifierOfEvent",
    "is_family_share": false,
    "offer_code": "free_month",
    "original_app_user_id": "OriginalAppUserID",
    "original_transaction_id": "1530648507000",
    "period_type": "NORMAL",
    "presented_offering_id": "OfferingID",
    "price": 2.49,
    "price_in_purchased_currency": 2.49,
    "product_id": "onemonth_no_trial",
    "purchased_at_ms": 1591121853000,
    "store": "APP_STORE",
    "subscriber_attributes": {
      "$Favorite Cat": {
        "updated_at_ms": 1581121853000,
        "value": "Garfield"
      }
    },
    "takehome_percentage": 0.7,
    "transaction_id": "170000869511114",
    "type": "INITIAL_PURCHASE"
  }
}

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

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

[String]

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.

entitlement_ids

[String]

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.

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.

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.

takehome_percentage

Double

The estimated percentage of the transaction price that will be paid out to developers after the Apple/Amazon/Google platform fee.

0.7 or 0.85

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

[String]

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

[String]

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.

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 cancelled through Apple support and received a refund, a Play Store subscription was refunded through RevenueCat, an Amazon subscription was refunded through Amazon support, or a web subscription was refunded.

✅

✅

✅

✅

❌

UNKNOWN

Apple did not provide the reason of the cancellation.

✅

❌

❌

❌

❌

SUBSCRIPTION_PAUSED

The subscription expired because it was paused (only EXPIRATION event)

❌

✅

❌

❌

❌

Testing

You can test your server side implementation by purchasing sandbox subscriptions or by issuing test webhook events through RevenueCat's dashboard.

When testing with sandbox purchases, the environment value will be SANDBOX. RevenueCat itself does not have sandbox and production environments, so this value is only determined by the type of transaction received from the store. The same customer in RevenueCat can have both sandbox and production transactions associated with their account.

Syncing Subscription Status

Webhooks are commonly used to keep a subscribers status in sync with RevenueCat across multiple systems. To simplify the logic of handling different webhook types, we recommend creating a polling system using the GET /subscribers REST API to sync the subscription status of the customer from RevenueCat to your database. Then, each webhook event can simply be a trigger to call this sync function. This approach has a few benefits and can make your system more robust and scalable.

Security and Best Practices

Authorization

You can configure the authorization header used for webhook requests via the dashboard. Your server should verify the validity of the authorization header for every notification.

Response Duration

If your server doesn't finish the response in 60s, RevenueCat will disconnect. We then retry up to 5 times. We recommend that apps respond quickly and defer processing until after the response has been sent.

Delivery Delays

Most webhooks are usually delivered within 5 to 60 seconds of the event occurring - cancellation events usually are delivered within 2hrs of the user cancelling their subscription. You should be aware of these delivery times when designing your app.

Future Proofing

You should be able to handle webhooks that include additional fields to what's shown here, including new event types. We may add new fields or event types in the future without changing the API version. We won't remove fields or events without proper API versioning and deprecation.

Sample Webhook Events

These are some representative samples of webhooks you might receive from RevenueCat. Keep in mind that webhooks can include additional fields to what's shown here.

{
  "event" : {
    "event_timestamp_ms" : 1601337615995,
    "product_id" : "com.revenuecat.myapp.monthly",
    "period_type" : "NORMAL",
    "purchased_at_ms" : 1601258901000,
    "expiration_at_ms" : 1601336705000,
    "environment" : "PRODUCTION",
    "entitlement_id" : "pro",
    "entitlement_ids" : [
      "pro"
    ],
    "presented_offering_id" : "defaultoffering",
    "transaction_id" : "100000000000000",
    "original_transaction_id" : "100000000000000",
    "app_user_id" : "$RCAnonymousID:12345678-1234-ABCD-1234-123456789123",
    "aliases" : [
      "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
      "user_1234"
    ],
    "offer_code": null,
    "original_app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "cancel_reason" : "CUSTOMER_SUPPORT",
    "currency" : "USD",
    "price" : -9.99,
    "price_in_purchased_currency" : -9.99,
    "subscriber_attributes" : {
      "$idfa" : {
        "value" : "12345678-1234-1234-1234-12345678912x",
        "updated_at_ms" : 1578018408238
      },
      "$appsflyerId" : {
        "value" : "1234567891234-1234567",
        "updated_at_ms" : 1578018408238
      }
    },
    "store" : "APP_STORE",
    "takehome_percentage" : 0.7,
    "type" : "CANCELLATION",
    "id" : "12345678-1234-1234-1234-12345678912"
  },
  "api_version" : "1.0"
}
{
  "event" : {
    "event_timestamp_ms" : 1601337601013,
    "product_id" : "com.revenuecat.myapp.monthly",
    "period_type" : "NORMAL",
    "purchased_at_ms" : 1598640647000,
    "expiration_at_ms" : 1601319047000,
    "environment" : "PRODUCTION",
    "entitlement_id" : "pro",
    "entitlement_ids" : [
      "pro"
    ],
    "presented_offering_id" : "defaultoffering",
    "transaction_id" : "100000000000002",
    "original_transaction_id" : "100000000000000",
    "app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "aliases" : [
      "$RCAnonymousID:12345678-1234-1234-1234-123456789123"
    ],
    "offer_code": "summer_special",
    "original_app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "currency" : "USD",
    "price" : 0,
    "price_in_purchased_currency" : 0,
    "subscriber_attributes" : {
      "$idfa" : {
        "value" : "12345678-1234-1234-1234-12345678912x",
        "updated_at_ms" : 1578018408238
      },
      "$appsflyerId" : {
        "value" : "1234567891234-1234567",
        "updated_at_ms" : 1578018408238
      }
    },
    "store" : "APP_STORE",
    "takehome_percentage" : 0.7,
    "type" : "BILLING_ISSUE",
    "id" : "12345678-1234-1234-1234-12345678912"
  },
  "api_version" : "1.0"
}
{
  "event" : {
    "event_timestamp_ms" : 1601338594769,
    "product_id" : "com.revenuecat.myapp.monthly",
    "period_type" : "NORMAL",
    "purchased_at_ms" : 1601304429682,
    "expiration_at_ms" : 1601311606660,
    "environment" : "PRODUCTION",
    "entitlement_id" : "subscription",
    "entitlement_ids" : [
      "subscription"
    ],
    "presented_offering_id" : "defaultoffering",
    "transaction_id" : "GPA.1234-1234-1234-12345",
    "original_transaction_id" : "GPA.1234-1234-1234-12345",
    "app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "aliases" : [
      "$RCAnonymousID:12345678-1234-1234-1234-123456789123"
    ],
    "offer_code": null,
    "original_app_user_id" : "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "new_product_id" : "com.revenuecat.myapp.yearly",
    "currency" : "USD",
    "price" : 0,
    "price_in_purchased_currency" : 0,
    "subscriber_attributes" : {},
    "store" : "PLAY_STORE",
    "takehome_percentage" : 0.70,
    "type" : "PRODUCT_CHANGE",
    "id" : "12345678-1234-1234-1234-12345678912"
  },
  "api_version" : "1.0"
}
{
  "event": {
    "event_timestamp_ms": 1601337615995,
    "product_id": "com.revenuecat.myapp.weekly",
    "period_type": "NORMAL",
    "purchased_at_ms": 1601417766000,
    "expiration_at_ms": 1602022566000,
    "environment": "PRODUCTION",
    "entitlement_id": "pro",
    "entitlement_ids": [
      "pro"
    ],
    "presented_offering_id": "defaultoffering",
    "transaction_id": "100000000000002",
    "original_transaction_id": "100000000000000",
    "app_user_id": "$RCAnonymousID:12345678-1234-1234-1234-123456789123",
    "aliases": [
      "$RCAnonymousID:12345678-1234-ABCD-1234-123456789123",
      "user_1234"
    ],
    "offer_code": "free_month",
    "original_app_user_id": "$RCAnonymousID:12345678-1234-ABCD-1234-123456789123",
    "cancel_reason": "UNSUBSCRIBE",
    "currency": "USD",
    "price": 0.0,
    "price_in_purchased_currency": 0.0,
    "subscriber_attributes": {
      "$idfa": {
        "value": "12345678-1234-1234-1234-12345678912x",
        "updated_at_ms": 1578018408238
      },
      "$appsflyerId": {
        "value": "1234567891234-1234567",
        "updated_at_ms": 1578018408238
      },
      "favorite_food": {
        "value": "pizza",
        "updated_at_ms": 1578018408238
      }
    },
    "store": "APP_STORE",
    "takehome_percentage": 0.7,
    "type": "CANCELLATION",
    "id": "12345678-ABCD-1234-ABCD-12345678912"
  },
  "api_version": "1.0"
}
{
 "event": {
     "type": "TRANSFER",
     "id": "CD489E0E-5D52-4E03-966B-A7F17788E432",
     "store": "APP_STORE",
     "transferred_from": ["00005A1C-6091-4F81-BE77-F0A83A271AB6"],
     "transferred_to": ["4BEDB450-8EF2-11E9-B475-0800200C9A66"],
     "event_timestamp_ms": 78789789798798
 },
 "api_version": "1.0"
}

Did this page help you?