# Connect revenue

> Bring subscription revenue into Tapll from App Store Server Notifications or RevenueCat, website payments from Stripe or Paddle, and link each purchase to the person who made it.

Source: https://tapll.com/docs/revenue · Updated 2026-10-23

Tapll reads revenue from two webhooks: Apple’s **App Store Server Notifications** and **RevenueCat**. Both describe the same purchases, so only one of them counts at a time.

## Which source counts

| Connected | What counts |
| --- | --- |
| App Store Server Notifications | Apple’s notifications. |
| RevenueCat only | RevenueCat’s webhooks. |
| Both | Apple’s notifications; RevenueCat events are stored but not counted, and the panel says so. |

Apple signs every notification and needs no third party, so it wins when both are connected.

## App Store Server Notifications

In **Settings → App Store Server Notifications**, create the notification URL and paste it into App Store Connect as the Production server URL. Apple accepts only one URL per app, so if your backend already receives notifications, enter its URL as the **forward** target: Tapll answers Apple first and forwards every notification unchanged, with retries and a log of each attempt.

Apple does not send a USD price, so revenue from notifications is shown per currency as charged.

## RevenueCat

Create a webhook in RevenueCat pointing at the URL from **Settings → RevenueCat**. Optionally add a **v2 secret key** (`sk_…`): Tapll checks it against RevenueCat before saving it, uses it to import subscriptions from before the webhook existed, and to look up purchasers.

## Link purchases to people

With **StoreKit 2**, pass Tapll’s install id as the `appAccountToken` on every purchase:

```swift
let result = try await product.purchase(
    options: Tapll.installUUID.map { [.appAccountToken($0)] } ?? []
)
```

With **RevenueCat**, set the subscriber attribute after `Purchases.configure`, and call `Tapll.identify(userId:)` with the same id you pass to `Purchases.logIn`:

```swift
if let id = Tapll.installId {
    Purchases.shared.attribution.setAttributes(["tapll_install_id": id])
}
```

> **The purchase token is not retroactive**
> Purchases made before `appAccountToken` ships can never be linked to a person. Ship it before the first release that sells anything, even if you connect revenue later.

Revenue totals do not depend on any of this: a purchase Tapll cannot link still counts, and the Revenue view shows how many subscriptions are unlinked.

## Stripe and Paddle (websites)

Payments on a website come from **Stripe** or **Paddle** (Billing). They are separate sources from the two above, and they never mix: Tapll shows website money in its own block, never added to in-app revenue or to the other web provider.

In **Settings**, open the Stripe or Paddle section and create the webhook URL. In Stripe, add it under Developers → Webhooks and bring the endpoint’s signing secret back to Tapll. In Paddle, add it under Developer tools → Notifications as a webhook destination and bring back the destination’s secret key. Tapll stores the secret but cannot check it until the first delivery arrives; the section says when it has.

To tie a payment to the visit that made it, pass the visit’s id into checkout. With Stripe, set it on the Checkout Session and on the subscription, because a Checkout Session’s metadata is not copied onto the subscription it creates:

```js
stripe.checkout.sessions.create({
  mode: 'subscription',
  // ...line_items, success_url, cancel_url
  metadata: { tapll_session_id: tapllSessionId, tapll_user_id: userId },
  subscription_data: {
    metadata: { tapll_session_id: tapllSessionId, tapll_user_id: userId },
  },
});
```

With Paddle.js, `customData` is copied onto the transaction and the subscription:

```js
Paddle.Checkout.open({
  items: [{ priceId: 'pri_…', quantity: 1 }],
  customData: {
    tapll_session_id: tapll.sessionId() ?? '',
    tapll_user_id: userId,
  },
});
```

Here `tapllSessionId` is `tapll.sessionId()` read in the browser and sent to your server. Tapll keeps no email, name or address from either provider; the customer is stored as a keyed hash. A payment with no visit attached still counts as revenue, it just has no channel.

## Strict anonymous projects

`Tapll.installUUID` is `nil` in strict anonymous mode, so purchases are counted but never linked to a person. Do not set `appAccountToken` from Tapll.
