Webhook event types and fields
Adapty sends webhooks in response to subscription events. This section defines these event types and the data contained in each webhook.
Webhook event types
You can send all event types to your webhook or choose only some of them. You can consult our Event flows to learn what kind of incoming data to expect and how to build your business logic around it. You can disable the event types you do not need when you set up your Webhook integration. There, you can also replace Adapty default event IDs with your own if required.
| Event name | Description |
|---|---|
| subscription_started | Triggered when a user activates a paid subscription without a trial period, meaning they are billed instantly. |
| subscription_renewed | Occurs when a subscription is renewed and the user is charged. This event starts from the second billing, whether it's a trial or non-trial subscription. |
| subscription_renewal_cancelled | A user has turned off subscription auto-renewal. The user retains access to premium features until the end of the paid subscription period. |
| subscription_renewal_reactivated | Triggered when a user reactivates subscription auto-renewal. |
| subscription_expired | Triggered when a subscription fully ends after being canceled. For instance, if a user cancels a subscription on December 12th but it remains active until December 31st, the event is recorded on December 31st when the subscription expires. |
| subscription_paused | Occurs when a user activates subscription pause (Android only). |
| subscription_deferred | Triggered when a subscription purchase is deferred, allowing users to delay payment while maintaining access to premium features. This feature is available through the Google Play Developer API and can be used for free trials or to accommodate users facing financial challenges. |
| non_subscription_purchase | Any non-subscription purchase, such as lifetime access or consumable products like in-game coins. |
| trial_started | Triggered when a user activates a trial subscription. |
| trial_converted | Occurs when a trial ends and the user is billed (first purchase). For example, if a user has a trial until January 14th but is billed on January 7th, this event is recorded on January 7th. |
| trial_renewal_cancelled | A user turned off subscription auto-renewal during the trial period. The user retains access to premium features until the trial ends but will not be billed or start a subscription. |
| trial_renewal_reactivated | Occurs when a user reactivates subscription auto-renewal during the trial period. |
| trial_expired | Triggered when a trial ends without converting to a subscription. |
| entered_grace_period | Occurs when a payment attempt fails, and the user enters a grace period (if enabled). The user retains premium access during this time. |
| billing_issue_detected | Triggered when a billing issue occurs during a charge attempt (e.g., insufficient card balance). |
| subscription_refunded | Triggered when a subscription is refunded (e.g., by Apple Support). |
| non_subscription_purchase_refunded | Triggered when a non-subscription purchase is refunded. |
| access_level_updated | Occurs when a user's access level is updated. |
Webhook event structure
Adapty will send you only those events you've chosen in the Events names section of the Integrations -> Webhooks page.
Webhook events are serialized in JSON. The body of a POST request to your server will contain the serialized event wrapped into the structure below. All events follow the same structure, but their fields vary based on the event type, store, and your specific configuration. User attributes are the custom user attributes you set up, so they contain what you've configured. Attribution data fields are the same for all event types as well, however, the list of attributions will depend on which attribution sources you use in your mobile app. See below an example of an event:
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": "UserIdInYourSystem",
"idfv": "00000000-0000-0000-0000-000000000000",
"idfa": "00000000-0000-0000-0000-000000000000",
"advertising_id": "00000000-0000-0000-0000-000000000000",
"profile_install_datetime": "2000-01-31T00:00:00.000000+0000",
"user_agent": "ExampleUserAgent/1.0 (Device; OS Version) Browser/Engine",
"email": "[email protected]",
"event_type": "subscription_started",
"event_datetime": "2000-01-31T00:00:00.000000+0000",
"event_properties": {
"store": "play_store",
"currency": "USD",
"price_usd": 4.99,
"profile_id": "00000000-0000-0000-0000-000000000000",
"cohort_name": "All Users",
"environment": "Production",
"price_local": 4.99,
"base_plan_id": "b1",
"developer_id": "onboarding_placement",
"ab_test_name": "onboarding_ab_test",
"ab_test_revision": 1,
"paywall_name": "UsedPaywall",
"proceeds_usd": 4.2315,
"variation_id": "00000000-0000-0000-0000-000000000000",
"purchase_date": "2024-11-15T10:45:36.181000+0000",
"store_country": "AR",
"event_datetime": "2000-01-31T00:00:00.000000+0000",
"proceeds_local": 4.2415,
"tax_amount_usd": 0,
"transaction_id": "0000000000000000",
"net_revenue_usd": 4.2415,
"profile_country": "AR",
"paywall_revision": "1",
"profile_event_id": "00000000-0000-0000-0000-000000000000",
"tax_amount_local": 0,
"net_revenue_local": 4.2415,
"vendor_product_id": "onemonth_no_trial",
"profile_ip_address": "10.10.1.1",
"consecutive_payments": 1,
"rate_after_first_year": false,
"original_purchase_date": "2000-01-31T00:00:00.000000+0000",
"original_transaction_id": "0000000000000000",
"subscription_expires_at": "2000-01-31T00:00:00.000000+0000",
"profile_has_access_level": true,
"profile_total_revenue_usd": 4.99,
"promotional_offer_id": null,
"store_offer_category": null,
"store_offer_discount_type": null
},
"event_api_version": 1,
"profiles_sharing_access_level": [{"profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem"}],
"attributions": {
"appsflyer": {
"ad_set": "Keywords 1.12",
"status": "non_organic",
"channel": "Google Ads",
"ad_group": null,
"campaign": "Social media influencers - Rest of the world",
"creative": null,
"created_at": "2000-01-31T00:00:00.000000+0000"
}
},
"user_attributes": {"Favourite_color": "Violet", "Pet_name": "Fluffy"},
"integration_ids": {"firebase_app_instance_id": "val1", "branch_id": "val2", "one_signal_player_id": "val3"},
"play_store_purchase_token": {
"product_id": "product_123",
"purchase_token": "token_abc_123",
"is_subscription": true
}
}
Event fields
Event parameters are the same for all event types.
| Field | Type | Description |
|---|---|---|
| advertising_id | UUID | Advertising ID (Android only). |
| attributions | JSON | Attribution data. Included if Send Attribution is enabled in Webhook settings. |
| customer_user_id | String | User ID from your app (UUID, email, or other ID) if you set it in your app code when identifying users. If you don't identify users in the app code or this specific user is anonymous (not logged in), this field is null. |
| String | User's email if you set it using the updateProfile method in the Adapty SDK or when creating/updating profiles via the server-side API. If you don't pass the email value to the SDK or API method, this field is null. | |
| event_api_version | Integer | Adapty API version (current: 1). |
| event_datetime | ISO 8601 | Event timestamp in ISO 8601 format (e.g., 2020-07-10T15:00:00.000000+0000). |
| event_properties | JSON | Event properties. |
| event_type | String | Event name in Adapty format. See Webhook event types for the full list. |
| idfa | UUID | Advertising ID (Apple only). IDFA in the profile in the Adapty Dashboard. It may be null if unavailable due to tracking restrictions, kids mode, or privacy settings. |
| idfv | UUID | Identifier for Vendors (IDFV), unique per developer. IDFV in the profile in the Adapty Dashboard. |
| integration_ids | JSON | User integration IDs if you set them using the setIntegrationIdentifier method in the Adapty SDK or when creating/updating profiles via the server-side API. null if unavailable or integrations are disabled. |
| play_store_purchase_token | JSON | Play Store purchase token, included if Send Play Store purchase token is enabled in Webhook settings. |
| profile_id | UUID | Profile ID automatically generated by Adapty for each profile. One Apple/Google ID can be associated with different profile IDs if you don't identify users or allow purchases before login. Learn more about the way Adapty works with parent/inheritor profiles. |
| profile_install_datetime | ISO 8601 | Installation timestamp in ISO 8601 format (e.g., 2020-07-10T15:00:00.000000+0000). |
| profiles_sharing_access_level | JSON | List of users sharing the access level excluding the current user profile. If sharing access levels is enabled for your app, this list includes other profiles that have been used with the same Apple/Google ID. Format:
|
| user_agent | String | Device browser user-agent. |
| user_attributes | JSON | Custom data you can set to enrich user profiles with app-specific information. Typically used to track user preferences (e.g., theme, language) or behavior flags (completed onboarding, feature usage). Formatted as key-value pairs where keys are strings and values can be strings or numbers (e.g., {"Favourite_color": "Violet", "Pet_name": "Fluffy"}). You can set custom attributes manually in the Adapty Dashboard for individual profiles, programmatically using the updateProfile method in the Adapty SDK, or via the server-side API when creating/updating profiles. Included if Send User Attributes is enabled in Webhook settings. While custom attribute values in the mobile app code can be set as floats or strings, attributes received via the server-side API or historical import may come in different formats. The boolean and integer values will be converted to floats in this case. |
Attributions
To send the attribution data, enable the Send Attribution option in the Integrations -> Webhooks page. If you've enabled sending attribution data and if you have set up attribution integrations, the data below will be sent with the event for every source. The same attribution data is sent to all event types.
{
"attributions": {
"appsflyer": {
"ad_set": "sample_ad_set_123",
"status": "non_organic",
"channel": "sample_channel",
"ad_group": "sample_ad_group_456",
"campaign": "sample_ios_campaign",
"creative": "sample_creative_789",
"created_at": "2000-01-31T00:00:00.000000+0000",
"network_user_id": "0000000000000-0000000"
}
}
}
| Field name | Field type | Description |
|---|---|---|
| ad_set | String | Attribution ad set. |
| status | String | Can be organic, non_organic, or unknown. |
| channel | String | Marketing channel name. |
| ad_group | String | Attribution ad group. |
| campaign | String | Marketing campaign name. |
| creative | String | Attribution creative keyword. |
| created_at | ISO 8601 date | Date and time of attribution record creation. |
| network_user_id | String | ID assigned to the user by the attribution source. |
Integration IDs
The following integration IDs are used now in events:
adjust_device_idairbridge_device_idamplitude_device_idamplitude_user_idappmetrica_device_idappmetrica_profile_idappsflyer_idbranch_idfacebook_anonymous_idfirebase_app_instance_idmixpanel_user_idpushwoosh_hwidone_signal_player_idone_signal_subscription_idtenjin_analytics_installation_idposthog_distinct_user_id
Play Store purchase token
This field includes all the data needed to revalidate a purchase, if necessary. It is sent only if the Send Play Store purchase token option is enabled in the Webhook integration settings.
| Field | Type | Description |
|---|---|---|
| product_id | String | The unique identifier of the product (SKU) purchased in the Play Store. |
| purchase_token | String | A token generated by Google Play to uniquely identify this purchase transaction. |
| is_subscription | Boolean | Indicates whether the purchased product is a subscription (true) or a one-time purchase (false). |
Event properties
Event properties can vary depending on the event type and even between events of the same type. For instance, an event originating from the App Store won’t include Android-specific properties like base_plan_id.
The Access Level Updated event has distinct properties, so we’ve dedicated a separate section to it. Similarly, we’ve separated Additional tax and revenue event properties, as they are specific to only certain event types.
For most event types
The event properties for most event types are consistent (except for Access Level Updated event, which is described in its own section). Below is a comprehensive table highlighting properties and indicating if they belong to specific events.
| Field | Type | Description |
|---|---|---|
| ab_test_name | String | Name of the Adapty A/B test where the transaction originated. |
| ab_test_revision | Integer | Revision of the A/B test where the transaction originated. |
| base_plan_id | String | Base plan ID in the Google Play Store or price ID in Stripe. |
| cancellation_reason | String | Possible reasons for cancellation: Present in the following event types: subscription_cancelled, subscription_refunded, and trial_cancelled. |
| cohort_name | String | The name of the audience that determined which paywall the user was shown. |
| consecutive_payments | Integer | The number of periods, that a user is subscribed to without interruptions. Includes the current period. |
| currency | String | Local currency. |
| developer_id | String | The ID of the placement where the transaction originated. |
| environment | String | Possible values are Sandbox or Production. |
| event_datetime | ISO 8601 date | The date and time of the event. The same as on the root level of the event. |
| original_purchase_date | ISO 8601 date | For recurring subscriptions, the original purchase is the first transaction in the chain, its ID called original transaction ID links the chain of renewals; later transactions are extensions of it. The original purchase date is the date and time of this first transaction. |
| original_transaction_id | String | For recurring subscriptions, this is the original transaction ID that links the chain of renewals. The original transaction is the first in the chain; later transactions are extensions of it. If no extensions, |
| paywall_name | String | Name of the paywall where the transaction originated. |
| paywall_revision | String | Revision of the paywall where the transaction originated. The default value is 1. |
| price_local | Float | Product price before Apple/Google cut in local currency. |
| price_usd | Float | Product price before Apple/Google cut in USD. |
| profile_country | String | Determined by Adapty, based on profile IP. |
| profile_event_id | UUID | Unique event ID that can be used for deduplication. |
| profile_has_access_level | Boolean | A boolean that indicates whether the profile has an active access level. |
| profile_id | UUID | Adapty-generated profile ID. The same as on the root level of the event. |
| profile_ip_address | String | Profile IP (can be IPv4 or IPv6, with IPv4 preferred when available). null if Collect users' IP addresses is disabled in the app settings. |
| profile_total_revenue_usd | Float | Total revenue for the profile with refunds subtracted from the revenue. |
| promotional_offer_id | String | The Adapty ID of the promotional offer used. You set this ID when you create an offer in the dashboard. |
| purchase_date | ISO 8601 date | The date and time of the product purchase. |
| rate_after_first_year | Boolean | Boolean indicates that the subscription qualifies for a reduced commission rate (typically 15%) after one year of continuous renewal. Commission rates vary based on program eligibility and country. See Store commission and taxes for details. |
| store | String | Store where the product was bought. Standard values: app_store, play_store, stripe, paddle. If you set custom store transactions using the server-side API, the value from the store parameter is used. |
| store_country | String | The country sent to us by the app store. |
| store_offer_category | String | Applied offer category. Possible values are introductory, promotional, winback. |
| store_offer_discount_type | String | Applied offer type. Possible values are free_trial, pay_as_you_go, and pay_up_front. |
| subscription_expires_at | ISO 8601 date | The Expiration date of subscription. Usually in the future. |
| transaction_id | String | Unique identifier for a transaction. |
| trial_duration | String | Duration of a trial period in days. Sent in a format " days", for example, "7 days". Present in the trial connected event types only: trial_started, trial_converted, trial_cancelled. |
| variation_id | UUID | Unique ID of the paywall where the purchase was made. |
| vendor_product_id | String | Product ID in the Apple App Store, Google Play Store, or Stripe. |
Additional tax and revenue event properties
The event properties related to taxes and revenue below are additional fields that apply only to certain event types. This means that the listed event types include the Event properties for most event types, along with the extra fields listed below.
Event types that have the tax and revenue event properties:
subscription_renewedsubscription_initial_purchasesubscription_refundednon_subscription_purchase
| Field | Type | Description |
|---|---|---|
| net_revenue_local | Float | Net revenue (income after Apple/Google cut and taxes) in local currency. |
| net_revenue_usd | Float | Net revenue (income after Apple/Google cut and taxes) in USD. |
| proceeds_local | Float | Product price after Apple/Google cut in local currency. |
| proceeds_usd | Float | Product price after Apple/Google cut. |
| tax_amount_local | Float | Tax amount deducted in local currency. |
| tax_amount_usd | Float | Tax amount deducted in USD. |
For Access Level Updated event
The Access Level Updated event is a specific webhook event generated only when the Webhook integration is active, and this event type is enabled. If enabled, it is sent to the configured Webhook and appears in the Event Feed. If not enabled, the event will not be created.
If you have enabled sharing access levels, the access level updated event will be sent for all profiles sharing the access level.
Use this event to update the user’s access level in your database, grant or revoke premium features on your backend, and keep access in sync across devices or platforms.
| Property | Type | Description |
|---|---|---|
| ab_test_name | String | Name of the A/B test where the transaction originated. |
| access_level_id | String | The ID of the access level. |
| activated_at | ISO 8601 date | Date and time when the access was latest activated. |
| active_introductory_offer_type | String | Type of introductory offer applied. Possible values are free_trial, pay_as_you_go, and pay_up_front. |
| active_promotional_offer_id | String | ID of promotional offer as indicated in the Product section of the Adapty Dashboard |
| active_promotional_offer_type | String | Type of promotional offer applied. Possible values are free_trial, pay_as_you_go, and pay_up_front. |
| base_plan_id | String | Base plan ID in the Google Play Store or price ID in Stripe. |
| billing_issue_detected_at | ISO 8601 date | Date and time of billing issue. |
| cancellation_reason | String | Possible reasons for cancellation: voluntarily_cancelled, billing_error, price_increase, product_was_not_available, refund, cancelled_by_developer, new_subscription_replace, upgraded, unknown, adapty_revoked. |
| cohort_name | String | Name of the audience to which the profile belongs. |
| currency | String | Local currency (defaults to USD). |
| developer_id | String | The ID of the placement where the transaction originated. |
| environment | String | Possible values are Sandbox or Production. |
| event_datetime | ISO 8601 date | The date and time of the event. |
| expires_at | ISO 8601 date | Date and time when the access will expire. |
| is_active | Boolean | Boolean indicating whether the access level is active. |
| is_in_grace_period | Boolean | Boolean indicating whether the profile is in the grace period. |
| is_lifetime | Boolean | Boolean indicating whether the access level is lifetime. |
| is_refund | Boolean | Boolean indicating whether the transaction is a refund. |
| original_purchase_date | ISO 8601 date | For recurring subscriptions, the original purchase is the first transaction in the chain, its ID called original transaction ID links the chain of renewals; later transactions are extensions of it. The original purchase date is the date and time of this first transaction. |
| original_transaction_id | String | For recurring subscriptions, this is the original transaction ID that links the chain of renewals. The original transaction is the first in the chain; later transactions are extensions of it. If no extensions, |
| paywall_name | String | Name of the paywall where the transaction originated. |
| paywall_revision | String | Revision of the paywall where the transaction originated. The default value is 1. |
| profile_country | String | Determined by Adapty, based on profile IP. |
| profile_event_id | UUID | Unique event ID that can be used for deduplication. |
| profile_has_access_level | Boolean | Boolean indicating whether the profile has an active access level. |
| profile_id | UUID | Adapty internal user profile ID. |
| profile_ip_address | String | Profile IP address of the user. |
| profile_total_revenue_usd | Float | Total revenue for the profile, refunds included. |
| purchase_date | ISO 8601 date | The date and time of product purchase. |
| renewed_at | ISO 8601 date | Date and time when the access will be renewed. |
| starts_at | ISO 8601 date | Date and time when the access level starts. |
| store | String | Store where the product was bought. Standard values: app_store, play_store, stripe, paddle. If you set custom store transactions using the server-side API, the value from the store parameter is used. |
| store_country | String | Country sent to Adapty by the app store. |
| subscription_expires_at | ISO 8601 date | Expiration date of the subscription. |
| transaction_id | String | Unique identifier for a transaction. |
| trial_duration | String | Duration of a trial period in days (e.g., "7 days"). |
| variation_id | UUID | An identifier of a variation, used to attribute purchases to this paywall. |
| vendor_product_id | String | Product ID in the store (Apple/Google/Stripe). |
| will_renew | Boolean | Indicates whether the paid access level will be renewed. |
Note that this structure may grow over time — with new data being introduced by us or by the 3rd parties we work with. Make sure that your code that processes it is robust enough and relies on specific fields rather than the entire structure.