# Superwall webhook

Send Superwall subscription lifecycle events to ActivationPal: trials, paid starts, renewals, cancellations, expirations, billing issues, one-time purchases, and refunds.

## 1. Choose the integration roles

An app can use Superwall for paywalls while RevenueCat handles purchases. Those are separate choices.

- If **Revenue provider = Superwall**, configure the signed webhook below. Do not also send RevenueCat webhooks to ActivationPal for this app.
- If **Revenue provider = RevenueCat** and **Paywall platform = Superwall**, keep the ActivationPal RevenueCat webhook and do not enable the ActivationPal Superwall webhook. RevenueCat may forward revenue to Superwall for Superwall's own reporting.

ActivationPal accepts one revenue source per app and derives a store-transaction key during provider changes. This prevents the same purchase being counted twice.

One ActivationPal app represents one store application. If a product ships separate iOS and Android Superwall applications, add each platform as its own ActivationPal app.

## 2. Join revenue to the device

Configure ActivationPal with the same UUID that Superwall uses. For apps without accounts, generate and persist an anonymous UUID, then identify it in both SDKs. Do not use an email address or another personal value. When the identity changes, update both SDKs together.

Native Swift:

```swift
ActivationPal.shared.configure(
    app: "YOUR_APP_SLUG",
    key: "YOUR_ACTIVATIONPAL_API_KEY",
    userId: Superwall.shared.userId
)
```

If you identify a signed-in user, use a UUID and pass the same value to both:

```swift
let userId = account.id.uuidString
Superwall.shared.identify(userId: userId)
ActivationPal.shared.setUserId(userId)
```

React Native / Expo uses Superwall's current provider and hook API:

```tsx
import { useEffect } from "react";
import { SuperwallProvider, useUser } from "expo-superwall";
import ActivationPal from "activationpal-react-native";

function RevenueIdentity({ userId }: { userId: string }) {
  const { identify } = useUser();
  useEffect(() => {
    if (!identify) return;
    void identify(userId).then(() => ActivationPal.setUserId(userId));
  }, [identify, userId]);
  return null;
}

export default function App() {
  return (
    <SuperwallProvider apiKeys={{ ios: "YOUR_SUPERWALL_API_KEY" }}>
      <RevenueIdentity userId={persistedAnonymousOrAccountUUID} />
      <RootNavigator />
    </SuperwallProvider>
  );
}
```

Flutter:

```dart
await Superwall.shared.identify(userId);
ActivationPal.setUserId(userId);
```

Native Android:

```kotlin
Superwall.instance.identify(userId)
ActivationPal.setUserId(userId)
```

Use current Superwall SDKs and UUID identifiers. On iOS, the UUID travels as the StoreKit app account token. Older Superwall SDKs can omit or vary the original app user id, and non-UUID identifiers fall back to Superwall's alias, so either case can prevent a device join.

## 3. Add the signed webhook

In ActivationPal, open app settings → **Superwall webhook**. Copy the app-specific URL:

```text
https://activationpal.com/api/superwall?app=YOUR_APP_SLUG
```

Then in Superwall:

1. Open **Integrations → Webhooks** and add the URL.
2. Enable the subscription and payment event types.
3. Open the endpoint details and copy its signing secret. It starts with `whsec_`.
4. Paste that signing secret into ActivationPal app settings and save it.

The signing secret is encrypted at rest and is never returned after saving. Superwall authenticates each delivery with `svix-id`, `svix-timestamp`, and `svix-signature`; ActivationPal verifies those headers against the untouched raw request body and rejects stale or modified requests.

## 4. Event semantics

For API compatibility, both revenue providers use the existing `rc_*` lifecycle names. The `source` field tells you which provider delivered the event, and `props.provider` is `superwall`.

`rc_initial_purchase`, `rc_renewal`, `rc_cancellation`, `rc_uncancellation`, `rc_expiration`, `rc_billing_issue`, `rc_product_change`, `rc_subscription_paused`, `rc_non_renewing_purchase`

- Trial start = `rc_initial_purchase` with `period_type: "TRIAL"`.
- Trial conversion = `rc_renewal` with `is_trial_conversion: true`.
- Refund = any event with negative `price` and `proceeds`.
- `price` is customer revenue in USD. `proceeds` is net USD after tax and store commission.
- Gross revenue sums positive prices. Net revenue sums signed proceeds, so refunds reduce it. Refund totals are the absolute value of negative price/proceeds.
- `price_in_purchased_currency` and `currency` preserve the original checkout amount.
- `PRODUCTION` becomes `env=release`; `SANDBOX` becomes `env=debug` and stays out of release numbers.

The complete verified Superwall envelope is retained in `props.provider_payload`. Superwall's event id and the derived store-transaction key make retries idempotent.

## 5. Verify

Use a sandbox store transaction, then check the ActivationPal live feed or agent API. Local StoreKit configuration transactions do not generate App Store server notifications; use a sandbox Apple account or TestFlight transaction on iOS.

```bash
curl -s "https://activationpal.com/api/v1/get_events?app=YOUR_APP_SLUG&source=superwall" \
  -H "Authorization: Bearer YOUR_AGENT_KEY"
```

Expect `source: "superwall"`. Sandbox events show `env: "debug"`; production events show `env: "release"`.
