diff --git a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-drawer-component.mdx b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-drawer-component.mdx
index ee7b2be..18c65f2 100644
--- a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-drawer-component.mdx
+++ b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-drawer-component.mdx
@@ -25,6 +25,8 @@ To toggle its open state, you can use a tap behavior on a button or another elem

+You can also bind a drawer's open state to another paywall variable. For example, use `state.didAbandonTransaction` to open a recovery offer drawer after the user cancels the App Store or Google Play purchase sheet. See [Abandoned Transaction Paywalls](/dashboard/guides/tips-abandoned-transaction-paywall) for the full setup.
+
The variable's name is derived by the node's unique identifier. You don't need to set or generally be aware of this value.
@@ -44,4 +46,4 @@ By default, drawers have a minimum height set. This is a general size that works

-
\ No newline at end of file
+
diff --git a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables.mdx b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables.mdx
index 1f3b45d..2076129 100644
--- a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables.mdx
+++ b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables.mdx
@@ -140,14 +140,32 @@ The values above apply to any referenced product. There is the notion of a **pri
For example, to reference the price of the selected product (i.e. one the user has clicked or tapped on within the paywall) — you could write `The selected product cost {{ products.selected.price }}`.
-There are also two more stock variables which deal with products, but aren't a part of a single product variable itself. They are referenced via the `products` variable:
+There are also stock variables that deal with products, but aren't part of a single product variable itself. They are referenced via the `products` variable:
| Property | Type | Example |
| ---------------------- | ------ | ---------- |
| Has Introductory Offer | Bool | True/False |
| Selected Index | Number | 0 |
-Use `products.hasIntroductoryOffer` to detect whether or not a user has a trial available. Further, `products.selectedIndex` represents the index of a selected products (i.e. primary would equal 0).
+Use `products.hasIntroductoryOffer` to detect whether or not a user has a trial available. Further, `products.selectedIndex` represents the index of a selected product (i.e. primary would equal 0).
+
+Superwall also exposes `products.abandoned` after a user cancels the store purchase sheet, and `products.purchased` after a transaction completes. Once those states are set, these variables point at the product the user abandoned or purchased, so you can use the same fields shown above:
+
+| Property | Type | Example |
+| -------- | ---- | ------- |
+| Abandoned Product Price | Text | `{{ products.abandoned.price }}` |
+| Abandoned Product Period | Text | `{{ products.abandoned.periodly }}` |
+| Purchased Product Price | Text | `{{ products.purchased.price }}` |
+| Purchased Product Period | Text | `{{ products.purchased.periodly }}` |
+
+
+
+| Property | Type | Example |
+| -------- | ---- | ------- |
+| Did Abandon Transaction | Bool | `{{ state.didAbandonTransaction }}` |
+| Did Complete Transaction | Bool | `{{ state.didCompleteTransaction }}` |
+
+Use `state.didAbandonTransaction` to react when a user opens the App Store or Google Play purchase sheet and then cancels before purchase. A common pattern is to bind a drawer's open state to this variable so a recovery offer appears inside the same paywall. See [Abandoned Transaction Paywalls](/dashboard/guides/tips-abandoned-transaction-paywall) for a full example.
@@ -325,7 +343,9 @@ Here are some common examples:

-2. **Testing a particular page in a paging paywall:** In this design, there are three distinct pages:
+2. **Testing abandoned transaction states:** To test a drawer or offer that appears after a canceled purchase, change `state.didAbandonTransaction` to `true`. If your copy references the abandoned product, choose a product by setting `products.abandonedProductId` to its product reference, such as `primary` or `secondary`.
+
+3. **Testing a particular page in a paging paywall:** In this design, there are three distinct pages:

diff --git a/content/docs/dashboard/dashboard-settings/overview-settings-access-controls.mdx b/content/docs/dashboard/dashboard-settings/overview-settings-access-controls.mdx
index 146f6ba..7337c67 100644
--- a/content/docs/dashboard/dashboard-settings/overview-settings-access-controls.mdx
+++ b/content/docs/dashboard/dashboard-settings/overview-settings-access-controls.mdx
@@ -88,7 +88,7 @@ Organization API keys use the same access model:
| Setting | What it controls |
| --- | --- |
-| Scopes | Which resources the key can read or write, such as paywalls, campaigns, products, webhooks, charts, users, assets, or access controls. |
+| Scopes | Which resources the key can read or write, such as paywalls, campaigns, products, webhooks, charts, users, assets, access controls, or ClickHouse analytics data. |
| Project Access | Whether the key can operate across all projects or only selected projects. |
Both checks must pass. For example, an API key with `paywalls:write` and **Restricted** access to one project can only update paywalls in that project.
@@ -107,6 +107,8 @@ Use **Settings > API Keys** to review each key's scopes, project access, creatio
Prefer restricted API keys for automation. Give each service only the scopes and projects it needs.
+Keys with `data:read` can use the [ClickHouse query API](/dashboard/guides/query-clickhouse) to run read-only SQL against your organization's analytics data.
+
### Troubleshooting
If a member cannot see a project, confirm that their project access mode is **All Projects** or that the project is selected in their restricted assignments.
diff --git a/content/docs/dashboard/guides/query-clickhouse.mdx b/content/docs/dashboard/guides/query-clickhouse.mdx
new file mode 100644
index 0000000..7ebdec4
--- /dev/null
+++ b/content/docs/dashboard/guides/query-clickhouse.mdx
@@ -0,0 +1,167 @@
+---
+title: "Query ClickHouse"
+description: "Use the Superwall API to query your organization's ClickHouse-backed analytics data."
+---
+
+The ClickHouse query API gives you direct SQL access to the same analytics data Superwall uses for charts and campaign results. Use it when you need flexible reporting, internal dashboards, or ad hoc analysis without maintaining a separate data warehouse.
+
+Requests are scoped to your organization and require an organization API key with the `data:read` scope. Superwall provisions a read-only ClickHouse user for your organization on first use, then applies row-level policies so queries only return data for your organization's applications.
+
+
+Treat `data:read` keys as sensitive. They can query analytics data for your organization, so create dedicated keys, store them in a secret manager, and revoke them when they are no longer needed.
+
+
+## Endpoint
+
+Use either `POST` or `GET`:
+
+| Method | Path | SQL location |
+| --- | --- | --- |
+| `POST` | `https://api.superwall.com/v2/organizations/{organization_id}/query` | Request body |
+| `GET` | `https://api.superwall.com/v2/organizations/{organization_id}/query` | `query` URL parameter |
+
+`POST` is recommended for most queries because SQL can be long and multiline.
+
+## Authentication
+
+1. Open **Settings > API Keys** in the Superwall dashboard.
+2. Create an organization API key.
+3. Give the key the `data:read` scope.
+4. Copy the token when Superwall shows it.
+
+Pass the token as a bearer token:
+
+```bash
+Authorization: Bearer YOUR_SECRET_TOKEN
+```
+
+The token must belong to the organization in the path. A key from another organization cannot query this endpoint.
+
+## Send a query
+
+Set your organization ID and API key:
+
+```bash
+export SUPERWALL_ORG_ID="123"
+export SUPERWALL_API_KEY="sk_..."
+```
+
+Run a query with `POST`:
+
+```bash
+curl "https://api.superwall.com/v2/organizations/$SUPERWALL_ORG_ID/query" \
+ --request POST \
+ --header "Authorization: Bearer $SUPERWALL_API_KEY" \
+ --data-binary "SELECT count() FROM sw.events_rep"
+```
+
+Run a query with `GET`:
+
+```bash
+curl --get "https://api.superwall.com/v2/organizations/$SUPERWALL_ORG_ID/query" \
+ --header "Authorization: Bearer $SUPERWALL_API_KEY" \
+ --data-urlencode "query=SELECT count() FROM sw.events_rep"
+```
+
+The response is the raw ClickHouse HTTP response. If you do not specify a format, ClickHouse returns its default text format. Add a `FORMAT` clause when you need JSON:
+
+```bash
+curl "https://api.superwall.com/v2/organizations/$SUPERWALL_ORG_ID/query" \
+ --request POST \
+ --header "Authorization: Bearer $SUPERWALL_API_KEY" \
+ --data-binary "
+ SELECT
+ name,
+ count() AS events
+ FROM sw.events_rep
+ WHERE ts >= now() - INTERVAL 7 DAY
+ GROUP BY name
+ ORDER BY events DESC
+ LIMIT 20
+ FORMAT JSONEachRow
+ "
+```
+
+## Use ClickHouse HTTP options
+
+The endpoint proxies ClickHouse HTTP requests after Superwall authenticates your organization API key. You can pass standard ClickHouse URL parameters, such as `query`, `database`, or `default_format`, through the query string:
+
+```bash
+curl --get "https://api.superwall.com/v2/organizations/$SUPERWALL_ORG_ID/query" \
+ --header "Authorization: Bearer $SUPERWALL_API_KEY" \
+ --data-urlencode "query=SELECT name, count() FROM events_rep GROUP BY name LIMIT 20" \
+ --data-urlencode "database=sw" \
+ --data-urlencode "default_format=JSONEachRow"
+```
+
+Superwall does not expose the generated ClickHouse username and password. Authenticate to the Superwall endpoint with your bearer token instead of connecting directly to the ClickHouse cluster.
+
+## Available tables
+
+Your read-only user can query the analytics tables Superwall exposes for customer reporting:
+
+| Table | Use it for |
+| --- | --- |
+| `sw.events_rep` | Raw Superwall events, including event name, metadata, properties, sandbox flag, application ID, and timestamp. |
+| `sw.events_hr_agg` | Hourly event aggregates. |
+| `sw.demand_score_events_rep` | Demand Score event data. |
+| `open_revenue.attributed_events_by_ts_rep` | Revenue and attribution events ordered by event time. |
+| `open_revenue.paywall_open_events_agg` | Aggregated paywall open events. |
+| `sw.subscription_status_rep` | Subscription status records. |
+| `sw.user_attributes_rep` | User attributes set through the SDK or paywall flows. |
+| `sw.applications_rep` | Application metadata available in ClickHouse. |
+
+Use ClickHouse introspection queries to inspect columns before writing a production query:
+
+```sql
+SHOW TABLES FROM sw;
+SHOW TABLES FROM open_revenue;
+DESCRIBE TABLE sw.events_rep;
+DESCRIBE TABLE open_revenue.attributed_events_by_ts_rep;
+```
+
+## Query JSON properties
+
+Some event details are stored in JSON strings such as `props` and `meta`. Use ClickHouse JSON functions to extract them:
+
+```sql
+SELECT
+ JSONExtractString(props, '$placement_name') AS placement,
+ count() AS opens
+FROM sw.events_rep
+WHERE name = 'paywall_open'
+ AND ts >= now() - INTERVAL 30 DAY
+GROUP BY placement
+ORDER BY opens DESC
+LIMIT 20
+FORMAT JSONEachRow;
+```
+
+## Limits
+
+Queries run as a read-only organization user with ClickHouse settings applied:
+
+| Limit | Value |
+| --- | --- |
+| Maximum execution time | 300 seconds |
+| Maximum threads | 4 |
+| Maximum memory | 8 GB |
+| Maximum bytes read | 20 GB |
+
+If a query times out or uses too much memory, narrow the date range, add filters on `applicationId`, `isSandbox`, or event `name`, and avoid selecting large JSON columns unless you need them.
+
+## Troubleshooting
+
+| Status | What to check |
+| --- | --- |
+| `401` | The request is missing a bearer token, or the token is invalid or revoked. |
+| `403` | The API key does not include the `data:read` scope. |
+| `404` | The requested organization resource could not be found. |
+| `429` | Too many requests were sent in a short period. Retry later. |
+| `500` | ClickHouse returned an unexpected error or Superwall could not proxy the request. Check the SQL and try a smaller query. |
+
+## Related
+
+- [Access Controls](/dashboard/dashboard-settings/overview-settings-access-controls)
+- [Charts](/dashboard/charts)
+- [Superwall Skill](/dashboard/guides/superwall-skill)
diff --git a/content/docs/dashboard/guides/tips-abandoned-transaction-paywall.mdx b/content/docs/dashboard/guides/tips-abandoned-transaction-paywall.mdx
index b587b04..e61d192 100644
--- a/content/docs/dashboard/guides/tips-abandoned-transaction-paywall.mdx
+++ b/content/docs/dashboard/guides/tips-abandoned-transaction-paywall.mdx
@@ -1,14 +1,65 @@
---
title: "Abandoned Transaction Paywalls"
-description: "Learn how to present a a paywall when a user starts to convert, but then cancels the transaction."
+description: "Learn how to respond when a user starts a purchase, then cancels the transaction."
---
-### What
-Transaction abandon discounts can boost revenue by offering discounts to users who start, but don't complete, in-app purchases. We've seen 25-40% of revenue come from this method in a few of our own apps, and it can be implemented in Superwall without an app update.
+When a user opens the store purchase sheet and dismisses it before completing the purchase, Superwall tracks a `transaction_abandon` event. You can respond to that in two ways:
-### Why
-Somewhere around only 50% of users complete in-app purchases once they start. Offering discounts to those who showed interest, but hesitated, can convert them into paying customers.
+1. Show another paywall with a `transaction_abandon` placement.
+2. Keep the user on the current paywall and reveal a drawer, offer, or survey using the `didAbandonTransaction` paywall state.
-### How
+## Show another paywall instead
-
\ No newline at end of file
+You can add `transaction_abandon` as a placement in a campaign. If a matching paywall is available, Superwall closes the current paywall and presents the new one.
+
+Use this approach when the recovery experience should be a completely separate paywall, such as a dedicated discount page, a transaction-abandon survey template, or a later campaign with its own audience filters.
+
+For campaign setup details and available audience filter parameters, see [`transaction_abandon`](/dashboard/dashboard-campaigns/campaigns-standard-placements#transaction_abandon).
+
+
+
+## Use `didAbandonTransaction` in the current paywall
+
+Use the `didAbandonTransaction` state when you want the recovery offer to feel like part of the same paywall instead of closing one paywall and opening another.
+
+`didAbandonTransaction` is a boolean state variable that Superwall manages for you. It starts as `false` when the paywall opens or when a new purchase begins. If the user cancels the store purchase sheet, Superwall sets it to `true`.
+
+You can use that state to open a drawer after the abandoned transaction:
+
+
+
+ In the paywall editor, add a [Drawer](/dashboard/dashboard-creating-paywalls/paywall-editor-drawer-component) element. Put the follow-up offer, survey, or personalized message inside the drawer.
+
+
+ Select the drawer and set its open state to use a dynamic value. Use the `state.didAbandonTransaction` variable as the condition so the drawer opens when the value is `true`.
+
+
+ Add a button inside the drawer that starts the purchase you want to offer next. For example, you might show the same product with clearer copy, a discounted product, or a lower-priced alternative.
+
+
+ Preview the paywall on a device, tap the purchase button, then dismiss the App Store or Google Play purchase sheet. The drawer should appear on the same paywall after the transaction is abandoned.
+
+
+
+
+ If you need to edit or preview the drawer in the paywall editor, open the **Variables** panel and
+ temporarily set `state.didAbandonTransaction` to `true`.
+
+
+## Personalize the recovery offer
+
+When a transaction is abandoned, Superwall also stores the abandoned product reference. This lets you personalize copy based on the product the user tried to buy.
+
+For example, if the user attempted to purchase the annual product, you can use the abandoned product variables to show annual-specific copy or pricing inside the drawer:
+
+```liquid
+Still interested in {{ products.abandoned.periodly }} access?
+```
+
+You can use the same product fields available for your other product variables, such as `products.abandoned.price`, `products.abandoned.periodly`, or `products.abandoned.trialPeriodText`.
+
+
+ `products.abandoned.*` refers to the product on the current paywall that the user attempted to
+ purchase. Campaign audience filters use a separate `abandoned_product_id` value, which is the
+ store product identifier.
+
diff --git a/content/docs/dashboard/meta.json b/content/docs/dashboard/meta.json
index 3827aee..2b681b3 100644
--- a/content/docs/dashboard/meta.json
+++ b/content/docs/dashboard/meta.json
@@ -26,6 +26,7 @@
"manage-account",
"---Guides---",
+ "guides/query-clickhouse",
"guides/superwall-skill",
"guides/superwall-mcp",
"guides/migrating-from-revenuecat-to-superwall",
diff --git a/content/docs/expo/guides/using-revenuecat.mdx b/content/docs/expo/guides/using-revenuecat.mdx
index d69eed0..8de949d 100644
--- a/content/docs/expo/guides/using-revenuecat.mdx
+++ b/content/docs/expo/guides/using-revenuecat.mdx
@@ -24,7 +24,10 @@ The easiest way to integrate RevenueCat with Superwall is using the `CustomPurch
```tsx
import { useEffect } from "react"
import { Platform } from "react-native"
-import Purchases, { PURCHASES_ERROR_CODE } from "react-native-purchases"
+import Purchases, {
+ PRODUCT_CATEGORY,
+ PURCHASES_ERROR_CODE,
+} from "react-native-purchases"
import {
CustomPurchaseControllerProvider,
SuperwallProvider,
@@ -55,13 +58,38 @@ function App() {
controller={{
onPurchase: async (params) => {
try {
- const products = await Purchases.getProducts([params.productId])
- const product = products[0]
+ const products = await Promise.all([
+ Purchases.getProducts([params.productId], PRODUCT_CATEGORY.SUBSCRIPTION),
+ Purchases.getProducts([params.productId], PRODUCT_CATEGORY.NON_SUBSCRIPTION),
+ ]).then((results) => results.flat())
+
+ const product =
+ products.find((product) => product.identifier === params.productId) ??
+ (params.platform === "android"
+ ? products.find(
+ (product) => product.identifier === `${params.productId}:${params.basePlanId}`
+ )
+ : undefined) ??
+ products[0]
if (!product) {
return { type: "failed", error: "Product not found" }
}
+ if (params.platform === "android" && product.subscriptionOptions?.length) {
+ const optionId = params.offerId
+ ? `${params.basePlanId}:${params.offerId}`
+ : params.basePlanId
+ const option = product.subscriptionOptions.find((option) => option.id === optionId)
+
+ if (!option) {
+ return { type: "failed", error: "Subscription option not found" }
+ }
+
+ await Purchases.purchaseSubscriptionOption(option)
+ return
+ }
+
await Purchases.purchaseStoreProduct(product)
} catch (error: any) {
if (error.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {
@@ -90,8 +118,11 @@ function App() {
)
+}
```
+On Android, `onPurchase` includes a `basePlanId` and may include an `offerId`. Use those values to find the matching RevenueCat subscription option and call `Purchases.purchaseSubscriptionOption(option)`. Calling `Purchases.purchaseStoreProduct(product)` for a Google Play subscription lets RevenueCat choose the product's default offer, which may not be the offer selected on the Superwall paywall.
+
### 2. Sync Subscription Status
Listen for RevenueCat subscription changes and update Superwall:
diff --git a/content/docs/images/adaptive_pricing_test_mode.jpg b/content/docs/images/adaptive_pricing_test_mode.jpg
new file mode 100644
index 0000000..896cfaf
Binary files /dev/null and b/content/docs/images/adaptive_pricing_test_mode.jpg differ
diff --git a/content/docs/images/adaptive_pricing_toggle.jpg b/content/docs/images/adaptive_pricing_toggle.jpg
new file mode 100644
index 0000000..b4a5ae3
Binary files /dev/null and b/content/docs/images/adaptive_pricing_toggle.jpg differ
diff --git a/content/docs/images/sign-in-with-apple-email-sources.png b/content/docs/images/sign-in-with-apple-email-sources.png
new file mode 100644
index 0000000..54eeab6
Binary files /dev/null and b/content/docs/images/sign-in-with-apple-email-sources.png differ
diff --git a/content/docs/images/stripe-add-superwall-domain.jpg b/content/docs/images/stripe-add-superwall-domain.jpg
new file mode 100644
index 0000000..d4f6e24
Binary files /dev/null and b/content/docs/images/stripe-add-superwall-domain.jpg differ
diff --git a/content/docs/images/stripe-payment-method-domains.jpg b/content/docs/images/stripe-payment-method-domains.jpg
new file mode 100644
index 0000000..d5b122d
Binary files /dev/null and b/content/docs/images/stripe-payment-method-domains.jpg differ
diff --git a/content/docs/images/stripe-settings-payments.png b/content/docs/images/stripe-settings-payments.png
new file mode 100644
index 0000000..aa68e09
Binary files /dev/null and b/content/docs/images/stripe-settings-payments.png differ
diff --git a/content/docs/web-checkout/meta.json b/content/docs/web-checkout/meta.json
index bcc6fe5..4cd16bd 100644
--- a/content/docs/web-checkout/meta.json
+++ b/content/docs/web-checkout/meta.json
@@ -9,6 +9,7 @@
"web-checkout-creating-an-app",
"web-checkout-configuring-stripe-keys-and-settings",
"web-checkout-adding-a-stripe-product",
+ "web-checkout-adaptive-pricing",
"web-checkout-stripe-one-time-purchases",
"web-checkout-creating-campaigns-to-show-paywalls",
diff --git a/content/docs/web-checkout/web-checkout-adaptive-pricing.mdx b/content/docs/web-checkout/web-checkout-adaptive-pricing.mdx
new file mode 100644
index 0000000..d21bdd1
--- /dev/null
+++ b/content/docs/web-checkout/web-checkout-adaptive-pricing.mdx
@@ -0,0 +1,110 @@
+---
+title: "Adaptive Pricing"
+description: "Let web checkout customers pay in a local currency with Stripe Adaptive Pricing."
+---
+
+Stripe Adaptive Pricing can localize the currency shown during web checkout. When it applies, Stripe calculates a localized price from the customer's location, handles currency conversion, and charges the customer in the presentment currency.
+
+This applies to both web checkout entry points:
+
+| Flow | What happens |
+| --- | --- |
+| Web2App | A customer opens a web checkout link and sees checkout pricing localized by Stripe. |
+| App2Web | A customer taps a Stripe product on an iOS paywall, leaves the app for checkout, and sees checkout pricing localized by Stripe. |
+
+
+Adaptive Pricing is controlled in Stripe, not in a Superwall campaign or paywall. Enable it separately for sandbox and live mode in your Stripe payment settings.
+
+
+## Enable Adaptive Pricing
+
+1. Open [Stripe Adaptive Pricing settings](https://dashboard.stripe.com/settings/adaptive-pricing).
+2. Enable Adaptive Pricing for Checkout in the mode you want to use.
+3. Repeat the setup in **Test mode** if you want to test sandbox purchases.
+4. Confirm your Stripe products use a currency that Stripe supports for Adaptive Pricing.
+
+
+
+Disabling Adaptive Pricing does not change Checkout Sessions that already converted or active subscriptions that are already billed in a customer's local currency.
+
+## How pricing is localized
+
+Stripe determines the presentment currency from the customer's location, then converts the checkout price in real time. The exchange rate is guaranteed for 24 hours.
+
+Adaptive Pricing can also make local payment methods available when those methods require a local currency. For cross-border subscriptions, Stripe supports Adaptive Pricing with card payments, Link, Apple Pay, and Google Pay.
+
+
+You are responsible for complying with laws that apply to localized pricing in your regions. Review Stripe's Adaptive Pricing guidance and consult your legal advisor when needed.
+
+
+## What customers see
+
+Customers see the localized amount in the Stripe checkout flow. Your Stripe product, Checkout Session, and webhooks can still reference the product's original integration currency.
+
+Use Superwall product variables as usual on the paywall. The final localized charge is shown when Stripe checkout opens.
+
+If you build a custom Stripe checkout page outside of Superwall with embedded components, follow Stripe's Adaptive Pricing requirements for Elements, including rendering the Currency Selector Element near the payment details or order total. Superwall-hosted web checkout handles the checkout page for Superwall web paywalls.
+
+## Reporting
+
+Stripe's Checkout Session and PaymentIntent objects keep the integration currency and amount. When a customer pays in a local currency, Stripe adds `presentment_details` to supported events, including:
+
+- `checkout.session.completed`
+- `payment_intent.succeeded`
+- `customer.subscription.created`
+
+Use `presentment_details.presentment_amount` and `presentment_details.presentment_currency` in Stripe when you need to inspect the amount and currency the customer saw.
+
+Superwall revenue exports and integration payloads include purchased currency fields such as `currencyCode` and `priceInPurchasedCurrency` when purchase currency data is available.
+
+## Test Adaptive Pricing
+
+Stripe supports testing local currency presentment with a location-formatted email address. Add `+location_XX` before the `@`, where `XX` is a two-letter country code.
+
+For example, use this email to test France:
+
+```plaintext
+test+location_FR@example.com
+```
+
+### Web2App
+
+Pass the test email through the web checkout link's `email` query parameter. URL-encode the `+` character as `%2B`:
+
+```plaintext
+https://caffeinepal.superwall.app/black-friday-promo?email=test%2Blocation_FR@example.com
+```
+
+
+
+### App2Web
+
+Set the user's `email` attribute before they start checkout:
+
+```swift
+Superwall.shared.setUserAttributes([
+ "email": "test+location_FR@example.com"
+])
+```
+
+If you also set `stripe_customer_id`, Stripe uses the existing customer. For testing Adaptive Pricing with a saved Stripe customer, set that customer's email in Stripe to the location-formatted test email, or omit `stripe_customer_id` during the test so Superwall can pass the email directly to Checkout.
+
+Use Stripe's normal test cards after the localized checkout page opens.
+
+## Troubleshooting
+
+| Issue | What to check |
+| --- | --- |
+| Checkout still shows the original currency | Confirm Adaptive Pricing is enabled in the correct Stripe mode, and test with a country Stripe supports. |
+| Sandbox works but live checkout does not | Enable Adaptive Pricing in live mode too. Stripe settings are mode-specific. |
+| App2Web test ignores the location email | Check whether `stripe_customer_id` is set. Existing Stripe customer data can take precedence over the email attribute. |
+| Payment methods changed | Some payment methods are only available for certain currencies. Adaptive Pricing can add or remove methods based on the selected presentment currency. |
+| Stripe events show two currencies | This is expected. Stripe keeps the integration currency on the Checkout Session and exposes the customer's local currency in `presentment_details`. |
+
+## Related
+
+- [Stripe Setup](/web-checkout/web-checkout-configuring-stripe-keys-and-settings)
+- [Web Checkout Links](/web-checkout/web-checkout-creating-campaigns-to-show-paywalls)
+- [App2Web](/web-checkout/web-checkout-direct-stripe-checkout)
+- [Sandbox Purchases](/web-checkout/web-checkout-testing-purchases)
+- [Stripe Adaptive Pricing](https://docs.stripe.com/payments/currencies/localize-prices/adaptive-pricing?payment-ui=embedded-components)
diff --git a/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx b/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx
index 8f2714c..652a9e5 100644
--- a/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx
+++ b/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx
@@ -51,6 +51,46 @@ This is the domain your paywalls will be shown from. This was set when the Strip

+### Apple Pay domain setup
+
+If you want Apple Pay to appear in Stripe Checkout, add your Superwall web paywall domain as a Stripe payment method domain. Stripe requires the exact domain that hosts checkout to be registered before domain-based payment methods such as Apple Pay can appear.
+
+For a Superwall web checkout app, register the domain shown in **Web Paywall Domain**. For example, if your Superwall checkout links use `https://caffeinepal.superwall.app`, register `caffeinepal.superwall.app` in Stripe.
+
+
+ Register the Superwall subdomain only. Do not include `https://`, a path, or the root
+ `superwall.app` domain.
+
+
+
+
+ In Stripe, open **Settings**, then select **Payments**.
+
+ 
+
+
+
+ In the Payments settings, open **Payment method domains**.
+
+ 
+
+
+
+ Click **Add a new domain**, enter your Superwall web paywall domain, then save it.
+
+ 
+
+
+
+After Stripe validates the domain, it should appear as enabled. Apple Pay may still be hidden if the customer is not using a supported device, browser, card, country, or currency.
+
+
+ Stripe settings are mode-specific. If you use sandbox checkout, repeat this setup with **Test mode**
+ enabled in Stripe. Then repeat it again in live mode before launching.
+
+
+For more details, see Stripe's [payment method domain registration docs](https://docs.stripe.com/payments/payment-methods/pmd-registration).
+
### Stripe Live Configuration
To connect Stripe with Superwall, you'll use the official Superwall Stripe app:
@@ -95,6 +135,10 @@ This section should say "Configured" at the top right if setup was successful:

+### Adaptive Pricing
+
+If you want web checkout customers to pay in their local currency, enable [Stripe Adaptive Pricing](/web-checkout/web-checkout-adaptive-pricing) in Stripe's payment settings after connecting your Stripe keys. Adaptive Pricing is configured in Stripe separately for live and sandbox mode.
+
### Stripe Sandbox Configuration
diff --git a/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx b/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx
index 03d26e2..5610568 100644
--- a/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx
+++ b/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx
@@ -78,6 +78,12 @@ Then, if the URL is visited, the audience filter matches from above — and we c
Of course, this is a simplistic example — but this is useful for personalization, seasonal events, influencer campaigns and more. Any query string parameter you pass can be used in the paywall, and in audience filters.
+### Localized checkout prices
+
+Web checkout links support [Stripe Adaptive Pricing](/web-checkout/web-checkout-adaptive-pricing). When Adaptive Pricing is enabled in Stripe, customers can see localized currency in the Stripe checkout flow based on their location.
+
+This does not change how you build campaigns or placements in Superwall. Create the web checkout link as usual, then let Stripe localize the checkout price when the customer starts payment.
+
### Automatically populating user emails in checkout flows
diff --git a/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx b/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx
index 358379a..81ffe40 100644
--- a/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx
+++ b/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx
@@ -15,7 +15,7 @@ Do not present Stripe Checkout inside your iOS app using an in-app browser, `SFS
- First, follow the [web checkout setup guide](/web-checkout#getting-setup) to create a Stripe app and configure your web checkout settings. Specifically, you'll need to complete the first three steps. This includes installing the [Superwall Stripe app](https://marketplace.stripe.com/apps/superwall) and setting up your app's settings.
+ First, follow the [web checkout setup guide](/web-checkout#getting-setup) to create a Stripe app and configure your web checkout settings. Specifically, you'll need to complete the first three steps. This includes installing the [Superwall Stripe app](https://marketplace.stripe.com/apps/superwall), setting up your app's settings, and adding your Superwall web paywall domain to Stripe if you want Apple Pay to appear in checkout.
Select a paywall and add a Stripe product to it. This lets users start an external browser checkout flow from the paywall. Stripe products are prepended with "stripe" in the product selector:
@@ -40,6 +40,18 @@ Do not present Stripe Checkout inside your iOS app using an in-app browser, `SFS
If you need to test checkout, learn how [here](/web-checkout/web-checkout-testing-purchases).
+### Apple Pay
+
+App2Web checkout opens from your iOS paywall into the Superwall-hosted Stripe checkout page. If you want Apple Pay to appear there, add your `*.superwall.app` web paywall domain to Stripe's payment method domains before testing or launching.
+
+See [Apple Pay domain setup](/web-checkout/web-checkout-configuring-stripe-keys-and-settings#apple-pay-domain-setup) for the full setup steps.
+
+### Localized checkout prices
+
+App2Web supports [Stripe Adaptive Pricing](/web-checkout/web-checkout-adaptive-pricing) for the external Stripe checkout step. Enable Adaptive Pricing in Stripe, then users who leave the app for checkout can see localized currency based on their location.
+
+Keep the campaign filter aligned with Apple's external purchase rules. Adaptive Pricing changes the currency shown during Stripe checkout; it does not change which users should be eligible to see an external purchase link in your app.
+
### Prefill customer information
When starting checkout from an iOS paywall (App2Web), you can prefill customer information in two ways:
diff --git a/content/docs/web-checkout/web-checkout-faq.mdx b/content/docs/web-checkout/web-checkout-faq.mdx
index 1c81446..f3b5212 100644
--- a/content/docs/web-checkout/web-checkout-faq.mdx
+++ b/content/docs/web-checkout/web-checkout-faq.mdx
@@ -17,20 +17,29 @@ Yes. By default, Superwall emails the address used during checkout with instruct
To turn these off, use the "Disable Superwall Emails" setting in your Stripe app settings — see [how to disable the activation link email](/support/web-checkout/3969573187-how-do-i-disable-the-activation-link-email-for-web-checkout).
-### My app uses Sign in with Apple. Do I need to do anything extra?
+### Do customers who use Sign in with Apple Hide My Email receive web checkout emails?
-Yes. If your app uses Sign in with Apple, some customers may check out using an Apple private relay email address (e.g. `abc123@privaterelay.appleid.com`). Emails sent to these addresses — including Superwall's activation link email — will **bounce** unless you register the Superwall sending domain as an authorized email sender in your Apple Developer account.
+Yes. If customers check out with an Apple private relay address, such as `abc123@privaterelay.appleid.com`, register Superwall as an allowed email source in Apple Developer.
-To fix this:
+Superwall sends web checkout emails from your app-specific Superwall address, such as `support+your-app-name@superwall.app`. These emails include activation and subscription management links. Apple can reject those emails for Hide My Email customers unless `superwall.app` is registered as an email source for Sign in with Apple.
-1. Go to [Apple Developer — Services Configuration](https://developer.apple.com/account/resources/services/configure).
-2. Under **Configure Sign in with Apple for Email Communication**, click **Email Senders**.
-3. Register `superwall.app` as an authorized domain (or add the specific sender address `support+@superwall.app`).
+Configure this before launching web checkout:
+
+1. Go to [Apple Developer Services Configuration](https://developer.apple.com/account/resources/services/configure).
+2. Open **Configure Sign in with Apple for Email Communication**.
+3. Add `superwall.app` as an email source. If you prefer to authorize a specific sender, add your app's Superwall sender address, such as `support+your-app-name@superwall.app`.
+
+
Once registered, Apple will allow emails from Superwall to be delivered to private relay addresses.
+For Apple's requirements, see [Configure private email relay service](https://developer.apple.com/help/account/capabilities/configure-private-email-relay-service).
+
- If you skip this step, any customer who uses "Hide My Email" during checkout will never receive their activation link email, and won't be able to redeem their purchase in your app without visiting the manage page manually.
+ If you skip this step, customers who use **Hide My Email** may not receive web checkout emails
+ from Superwall. If customers already checked out before the email source was registered, ask them
+ to request a new link from your `https://{your-domain}.superwall.app/manage` page after the sender
+ is configured.
### What happens if a user taps the redemption link multiple times or shares it?
diff --git a/content/docs/web-checkout/web-checkout-managing-memberships.mdx b/content/docs/web-checkout/web-checkout-managing-memberships.mdx
index 153a2ad..d8997f4 100644
--- a/content/docs/web-checkout/web-checkout-managing-memberships.mdx
+++ b/content/docs/web-checkout/web-checkout-managing-memberships.mdx
@@ -13,6 +13,14 @@ By default, after a successful checkout, Superwall emails the address used at ch

+
+ If your app uses Sign in with Apple and customers may check out with **Hide My Email**, configure
+ Apple's private email relay before launching web checkout. Apple can reject Superwall web checkout
+ emails sent to `privaterelay.appleid.com` addresses unless `superwall.app` is registered as an
+ email source. See [Web Checkout FAQ](/web-checkout/web-checkout-faq#do-customers-who-use-sign-in-with-apple-hide-my-email-receive-web-checkout-emails)
+ for setup steps.
+
+
If you want to disable these emails, use the "Disable Superwall Emails" setting in your Stripe app settings — see [how to disable the activation link email](/support/web-checkout/3969573187-how-do-i-disable-the-activation-link-email-for-web-checkout).
When this page is visited, users enter the email they used during checkout to receive a link to manage their purchase:
diff --git a/content/docs/web-checkout/web-checkout-testing-purchases.mdx b/content/docs/web-checkout/web-checkout-testing-purchases.mdx
index af4fe35..c687939 100644
--- a/content/docs/web-checkout/web-checkout-testing-purchases.mdx
+++ b/content/docs/web-checkout/web-checkout-testing-purchases.mdx
@@ -40,3 +40,15 @@ To test a purchase:

This will allow you to checkout and go through the entire flow to debug issues, test it out on a device and more.
+
+### Testing Adaptive Pricing
+
+If you enabled [Stripe Adaptive Pricing](/web-checkout/web-checkout-adaptive-pricing), test local currency presentment with a location-formatted email address. Add `+location_XX` before the `@`, where `XX` is a two-letter country code.
+
+For Web2App, pass the email through the checkout link and URL-encode the `+` character:
+
+```plaintext
+https://caffeinepal.superwall.app/black-friday-promo?email=test%2Blocation_FR@example.com
+```
+
+For App2Web, set the user's `email` attribute to a location-formatted email before starting checkout. If you also set `stripe_customer_id`, use a Stripe test customer whose email includes the same `+location_XX` suffix.