Webhooks are only available on paid plans.
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
To start receiving webhook events, register your server URL for your app using RevenueCat's dashboard.
One webhook per app
Each of your apps has a webhook URL field you can set. This way you can decide which of your apps need server side notifications, and where they should be sent.
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.
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.
Event types
Webhook Event Type | Description | App Store | Play Store | Web | Promo |
---|---|---|---|---|---|
| Test event issued through the RevenueCat dashboard. | ✅ | ✅ | ❌ | ❌ |
| A new subscription has been purchased. | ✅ | ✅ | ✅ | ❌ |
| A customer has made a purchase that will not auto-renew. | ✅ | ✅ | ✅ | ✅ |
| 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. | ✅ | ✅ | ✅ | ❌ |
| A subscriber has changed the product of their subscription. | ✅ | ✅ | ✅ | ❌ |
| A subscription has been cancelled. | ✅ | ✅ | ✅ | ✅ |
| Auto-renew status has been re-enabled for a subscription. | ✅ | ✅ | ❌ | ❌ |
| There has been a problem trying to charge the subscriber. This does not mean the subscription has expired. | ✅ | ✅ | ✅ | ❌ |
| Deprecated. A new | ||||
| A subscription has been paused. | ❌ | ✅ | ❌ | ❌ |
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_user_id": "yourCustomerAppUserID",
"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,
"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 |
---|---|---|---|
| String |
| |
| String | Unique identifier of the event. | |
| Integer | The time that the event was generated. Does not necessarily coincide with when the action that triggered the event occurred (purchased, cancelled, etc). | |
| String | Last seen app user id of the subscriber. | |
| String | The first app user id used by the subscriber. | |
| [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 thealiases
array.
If we have to retry a webhook for any reason, the retry will have the same
id
andevent_timestamp_ms
of the first attempt.
Subscription lifecycle events fields
Field | Type | Description | Possible Values |
---|---|---|---|
| String | Product identifier of the subscription. | |
| [String] | Entitlement identifiers of the subscription. | It can be |
| String | Deprecated. See | Deprecated. See |
| String | Period type of the transaction. |
|
| Integer | Time when the transaction was purchased. Measured in milliseconds since Unix epoch | |
| Integer | Only available for 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 |
| 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 |
| Integer | The time when an Android subscription would resume after being paused. Measured in milliseconds since Unix epoch. | |
| String | Store the subscription belongs to. |
|
| String | Store environment. |
|
| Boolean | Only available for Whether the previous transaction was a free trial or not. |
|
| String | Only available for See Cancellation Reasons. |
|
| String | Product identifier of the new product the subscriber has switched to. Only available for App Store subscriptions and | |
| String | Not available for apps using legacy entitlements. The identifier for the offering that was presented to the user during their initial purchase. | |
| Double | The USD price of the transaction. | Can be Can be negative for refunds. |
| String | The ISO 4217 currency code that the product was purchased in. |
Can be |
| Double | The price of the transaction in the currency the product was purchased in. | Can be Can be negative for refunds. |
| Double | The estimated percentage of the transaction price that will be paid out to developers after the Apple/Google platform fee. |
|
| Map of attribute names to attribute objects. For more details see the subscriber attributes guide. | ||
| String | Transaction identifier from Apple/Google. | |
| String |
| |
| Boolean | Indicates if the user made this purchase or if it was shared to them via Family Sharing. |
Always false for non-Apple purchases. |
Cancellation Reasons
Reason | Description | App Store | Play Store | Web | Promo |
---|---|---|---|---|---|
| Subscriber cancelled voluntarily. This event fires when a user unsubscribes, not when the subscription expires. | ✅ | ✅ | ✅ | ❌ |
| Apple or Google could not charge the subscriber using their payment method. | ✅ | ✅ | ❌ | ❌ |
| Developer cancelled the subscription. | ❌ | ✅ | ❌ | ✅ |
| Subscriber did not agree to a price increase. | ✅ | ❌ | ❌ | ❌ |
| Customer cancelled through Apple support and received a refund, or a Google subscription was refunded through RevenueCat. | ✅ | ✅ | ❌ | ❌ |
| Apple did not provide the reason of the cancellation. | ✅ | ❌ | ❌ | ❌ |
Testing
You can test your server side implementation by purchasing sandbox subscriptions or by issuing test webhook events through RevenueCat's dashboard.
Sandbox notifications
Keep in mind that not all the events are guaranteed to be sent for sandbox subscriptions. This is due to limitations in the sandbox testing environments.
Security and best practices
Tracking Subscription Status
Webhooks are commonly used to keep a subscribers status in sync with RevenueCat across multiple systems. The best way to keep a subscription status up-to-date is by updating your system with the entitlement_ids
and expiration_at_ms
from every webhook event. By simplifying your logic to rely on just these attributes the event type no longer matters. Note that expiration_at_ms
for PRODUCT_CHANGE
events will correspond to the expiration of the product the customer is changing from. You can safely ignore PRODUCT_CHANGE
events if the expiration date of the old product isn't needed. More details about this here.
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"
],
"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"
],
"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"
],
"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"
],
"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"
}
Updated 4 days ago