> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequenzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adapty Integration

> Turn Adapty subscription webhooks into subscriber events, tags, and revenue attributes

Connect Adapty to trigger automations from in-app purchases on the App Store, Google Play, Stripe, and Paddle. Trials, renewals, cancellations, billing issues, and refunds become Sequenzy events, and each contact gets `mrr`, `ltv`, and billing attributes, without running your own webhook relay.

## What is Adapty?

[Adapty](https://adapty.io) handles in-app subscriptions and paywalls for iOS, Android, and web apps. It validates every store's purchases and sends one consistent webhook for every subscription change.

## How contacts are matched

Adapty identifies people by **customer user ID** and its own **profile ID**, not email. Sequenzy matches each event to a contact in this order:

1. A contact whose `externalId` is the customer user ID you pass to Adapty's `identify()`, or the Adapty profile ID
2. The profile's email, set with `updateProfile` or Adapty's server API
3. An `email` user attribute on the event
4. A customer user ID that is itself an email address

If your backend already creates contacts in Sequenzy, set their `externalId` to the same customer user ID you pass to `identify()`. Otherwise, set the email on the Adapty profile:

```swift theme={null}
try await Adapty.identify(user.id)
let params = AdaptyProfileParameters.Builder()
    .with(email: user.email)
    .build()
try await Adapty.updateProfile(params: params)
```

```kotlin theme={null}
Adapty.identify(user.id) { error -> }
val params = AdaptyProfileParameters.Builder()
    .withEmail(user.email)
    .build()
Adapty.updateProfile(params) { error -> }
```

Adapty includes the profile's email in every webhook, so it reaches Sequenzy with the next event. Events for profiles that match nothing, such as anonymous profiles without an email, are skipped and listed as **skipped** in the integration's **Activity**, so you can see which users need an email or `externalId`.

To fill the contact's name, set `firstName` and `lastName` custom attributes (or a single `name`) on the profile and turn on **Send user attributes** in Adapty's webhook settings.

## Connecting Adapty

### Step 1: Copy the endpoint URL and authorization value

1. In Sequenzy, go to **Settings → Integrations** and click **Connect** next to Adapty
2. Copy the **Endpoint URL** from the dialog:
   ```
   https://api.sequenzy.com/api/webhooks/adapty/{your-sequenzy-company-id}
   ```
3. Copy the **Authorization header value**. Sequenzy generates a random one for you. If your Adapty webhook already has a value, paste that instead (at least 16 characters)

### Step 2: Connect in Sequenzy first

Click **Connect Adapty** in the Sequenzy dialog. Do this before you save in Adapty: Adapty sends a verification request when you save, and Sequenzy only accepts it once the authorization value is saved.

### Step 3: Turn on the webhook in Adapty

1. In the Adapty dashboard, open **Integrations → Webhook** and turn the integration on
2. Paste the endpoint URL into **Production endpoint URL**, and the authorization value into **Authorization header value for production endpoint**
3. To test with sandbox purchases, paste the same two values into the sandbox fields
4. Keep Adapty's default event names
5. Click **Save**

Adapty sends an empty verification request when you save. It appears in Sequenzy's **Activity** next to Adapty as "Adapty verification request received. The connection works".

### Step 4: Confirm it works

Make a sandbox purchase in your app, then open **Activity** next to Adapty in Sequenzy. The event appears as skipped with "Sandbox event received. The connection works". Sandbox purchases never change contacts, tags, or revenue.

## Tracked events

| Event | Triggered when |
| - | - |
| `saas.trial_started` | `trial_started` |
| `saas.trial_will_end` | Sequenzy's own reminder, 3 days before a live trial ends, or right away for trials of 3 days or less |
| `saas.trial_ended` | `trial_converted`: the trial converts to its first paid period |
| `saas.trial_cancelled` | `trial_renewal_cancelled`: auto-renew is turned off during a trial |
| `saas.purchase` | `subscription_started`, `subscription_renewed`, `trial_converted`, or `non_subscription_purchase` |
| `saas.purchase.monthly` | The purchase was a monthly subscription |
| `saas.purchase.yearly` | The purchase was an annual subscription |
| `saas.payment_failed` | `billing_issue_detected`: the store could not charge a renewal and may retry |
| `saas.cancelled` | `subscription_renewal_cancelled`: auto-renew is turned off on a paid subscription; access lasts until expiration |
| `saas.churn` | `subscription_expired` or `trial_expired`, or `subscription_refunded`, because a refund ends access in Adapty |
| `saas.refund` | `subscription_refunded` or `non_subscription_purchase_refunded` |

`subscription_renewal_reactivated` and `trial_renewal_reactivated` clear a pending cancellation without an event. `subscription_paused` (Google Play) removes `mrr` without `saas.churn`; the next `subscription_renewed` restores it. A refund or expiration with `cancellation_reason` `upgraded` or `new_subscription_replace` is a plan change, never `saas.refund` or `saas.churn`. On Google Play, Stripe, and Paddle it removes the old plan's `mrr`, and the new plan's `subscription_started` adds its own. On the App Store both plans share one subscription, so it changes nothing and the new plan's purchase sets the `mrr`.

`subscription_active`, `trial_active`, `entered_grace_period`, `subscription_deferred`, and `access_level_updated` are acknowledged without events. `entered_grace_period` always follows `billing_issue_detected`, which already sends `saas.payment_failed`. Grants of `adapty_server_side_product`, `adapty_dashboard_product`, and `adapty_promotion` are neither purchases nor churn. There is no `saas.upgrade` or `saas.downgrade` for Adapty.

Every event includes the Adapty context in its properties, for filters and merge tags: `productId`, `store`, `environment`, `periodType`, `appUserId` (the customer user ID, or the profile ID for anonymous profiles), `countryCode`, `providerEventType` (Adapty's event name, such as `subscription_renewed`), `cancelReason` (on cancellations), `expirationReason` (on expirations), `isTrialConversion` (on renewals), `offerCode` when present, plus `amount` and `currency` on purchases.

## Synced attributes

| Attribute | Type | Description |
| - | - | - |
| `mrr` | Number | Monthly recurring revenue in US dollars, from the latest paid period with a known interval |
| `ltv` | Number | Lifetime value in US dollars: purchases minus refunds |
| `adaptyCustomerId` | String | The customer user ID, or the Adapty profile ID for anonymous profiles |
| `adaptyProfileId` | String | The Adapty profile ID |
| `adaptyStore` | String | Store of the latest purchase: `APP_STORE`, `PLAY_STORE`, `STRIPE`, or `PADDLE` |
| `billingPlanId` | String | The store product ID of the subscription |
| `billingInterval` | String | `week`, `month`, `year`, or `one_time`, inferred from the purchase |
| `billingPeriodEnd` | String | When the current period ends |
| `trial_ends_at` | String | When a free trial ends |

Contacts are also tagged `trial`, `customer`, `cancelled`, or `churned` as their subscription changes.

### How revenue is calculated

* Amounts use the price in the customer's currency, converted to US dollars. When the currency can't be converted, Adapty's own US dollar price is used
* Prices include the store's commission and tax. They are not your take-home proceeds
* MRR comes from the latest paid period whose interval is known
* MRR is removed at expiration or refund, not at cancellation, because access continues until the period ends
* Refunds lower `ltv`, except the old plan's prorated refund in a plan change, which Sequenzy doesn't subtract

## Things to know

* **No backfill.** Adapty's webhook only sends new events, so existing subscribers update as their next event arrives (usually their next renewal). Sync Revenue is not available for Adapty
* **Sandbox is ignored.** Sandbox events confirm the connection in Activity but never update contacts or revenue
* **Keep default event names.** Adapty lets you rename events. Sequenzy only recognizes the defaults; renamed events are listed as skipped in Activity
* **Intervals are inferred** from the length of the purchased period, or from words like `monthly`, `annual`, or `1y` in the product ID when the period doesn't tell (trials, introductory prices, unusual lengths). A purchase with no inferable interval counts toward `ltv` but not `mrr`
* **Connect one source per app.** If RevenueCat or Superwall already sends these purchases to Sequenzy, keep one connected, or each purchase counts twice
* **One connection per workspace.** Reconnecting replaces the authorization value
* **Contacts don't move.** If a subscription later resolves to a different contact (a changed email or a contact created afterwards with that `externalId`), the earlier contact keeps its `mrr` for that subscription. Call `identify()` and set the email before the first purchase to avoid this
* **Late deliveries** never overwrite newer subscription state. A late purchase still counts toward `ltv`, and a refund delivered after the subscription renewed again lowers `ltv` without `saas.churn`
* **Retries are safe.** Adapty retries failed deliveries with the same `profile_event_id`, and Sequenzy records each event once. When one cancellation sends both `subscription_expired` and `subscription_refunded`, `saas.churn` is recorded once

## Troubleshooting

### Adapty can't save the webhook

Adapty checks the URL when you click **Save**. Connect Adapty in Sequenzy first, and make sure the authorization value in Adapty is exactly the one saved in Sequenzy. The rejected attempt appears as failed in Sequenzy's **Activity** with the reason.

### Events are skipped with "No contact matched this Adapty profile"

The profile has no email and no contact has its customer user ID as `externalId`. Call `identify()` and `updateProfile` with the email after login, or set the contact's `externalId` to the customer user ID you pass to Adapty. Anonymous profiles that never log in can't be matched.

### Deliveries fail with an authorization error

The authorization value in Adapty doesn't match the one saved in Sequenzy. Paste the same value into both, or reconnect Adapty in Sequenzy with the value Adapty sends.

### Nothing appears in Activity

Check that the webhook in Adapty is turned on and points at the exact URL from the Sequenzy dialog, and that you tested with a sandbox or real purchase. Events for sandbox purchases only go to the sandbox endpoint URL.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.