Skip to main content

Overview

SpendOwl automatically observes StoreKit transactions — both StoreKit 1 and StoreKit 2 — to track revenue for ROAS calculation. This works passively alongside other subscription SDKs like RevenueCat or Adapty.

Automatic Tracking

Purchase tracking starts automatically when you call configure():
No additional code required for subscriptions and non-consumables — SpendOwl observes those transactions automatically. Apps that sell consumables need one Info.plist key; see Consumables.

How It Works

  1. User completes a purchase. SpendOwl observes it live where StoreKit reports it — this path is best effort, because Apple does not deliver the result of a direct Product.purchase() call to Transaction.updates
  2. On every launch SpendOwl re-scans transaction history and picks up anything the live path missed. Subscriptions and non-consumables always stay in that history; consumables only do under the condition described in Consumables
  3. The transaction identifiers are extracted — see What the SDK Sends
  4. The attribution-to-transaction link is sent to SpendOwl servers
  5. Revenue for that transaction is resolved server-side from Apple’s App Store Server Notifications
  6. ROAS is calculated and displayed in your dashboard

What the SDK Sends

The SDK is deliberately minimal and read-only. It sends only the link between an attributed install and a transaction — no prices, no currency, no refund state:
If you inspect the SDK’s outgoing events you will see price, currency and countryCode as null. That is expected. Revenue data comes from Apple to our servers directly, which is both more accurate and impossible to tamper with from the client.

Tracked Transaction Types

The table below is what SpendOwl reports for each product type at the product level. It is not a description of the SDK payload — see What the SDK Sends for that.

Consumables

Only relevant if your app sells consumable products. Subscriptions and non-consumables need nothing beyond the setup above. Add this to your app’s Info.plist:
Why. StoreKit removes a consumable from transaction history the moment your app calls finish() on it — which most apps do as soon as they grant the content. SpendOwl re-scans that history on every launch to catch purchases it missed live, so without this key a missed consumable is missed permanently, while subscriptions and non-consumables are always recovered. Setting it to true keeps finished consumables in history on iOS 18 and later, and SpendOwl picks them up with no further work from you. There is no code to call — this is a one-time project setting. SpendOwl cannot add the key for you: Swift packages cannot contribute entries to your app’s Info.plist, and StoreKit reads it from the app bundle. Enable SpendOwl.enableLogging during integration and the SDK will tell you if the key is missing.
On iOS 17 and earlier this is not possible at all. Apple provides no way to see a finished consumable, so a consumable that isn’t captured at the moment of purchase cannot be recovered. SpendOwl does not ship a manual reporting API to work around it. As of mid-2026 iOS 18+ covers roughly 97% of devices.

Currency Handling

SpendOwl handles currency conversion automatically, server-side. The SDK does not read or send prices:
  • Apple reports the purchase in the currency of the user’s storefront
  • SpendOwl converts it to USD using current exchange rates
  • ROAS is always displayed in USD for consistency
Exchange rates are updated daily from European Central Bank data.

Works with Other SDKs

SpendOwl uses passive observation and doesn’t interfere with:
  • RevenueCat — Both can observe the same transactions
  • Adapty — No conflicts
  • Qonversion — Works alongside
  • Custom StoreKit code — No changes needed

Revenue Attribution

Purchases are attributed to campaigns:
  1. User installs from an Apple Search Ads campaign
  2. Attribution is captured by SpendOwl
  3. User makes a purchase (days or weeks later)
  4. Revenue is linked to the original campaign
This enables accurate ROAS calculation: Revenue / Ad Spend = ROAS

User ID Reporting

When a user ID is set, purchases are reported under that ID:
Setting a user ID does not re-key anything. Linkage always runs on the anonymous ID, so attribution and purchases stay correctly connected whether you call setUserId() early, late (after login), or never. The ID you pass is reporting metadata — it makes purchases searchable by your own user identifier.
See User Identity for more details.

Server-Side Validation

All transactions are validated server-side:
  • Transaction receipts are verified with Apple
  • Fraudulent transactions are filtered
  • Only valid revenue counts toward ROAS

Debugging Purchases

Enable logging to see purchase events:
The logs carry transaction identifiers, not amounts — the SDK never sees the price. Check your dashboard to confirm the revenue that was attached to a transaction.
Logging is compiled out of release builds, so a TestFlight or App Store build prints nothing even with enableLogging set. Verify your integration in a debug build.
See Debugging for more logging options.

FAQ

No. SpendOwl observes transactions automatically. Just ensure the SDK is configured before purchases occur.
Almost certainly the missing Info.plist key. Without SKIncludeConsumableInAppPurchaseHistory, a consumable that isn’t captured at the moment of purchase can never be recovered, because StoreKit drops it from history as soon as your app finishes the transaction. See Consumables.
Sandbox purchases are tracked separately and don’t affect production ROAS. Use a test API key in development.
StoreKit 1 is tracked too — no migration needed. SpendOwl registers an SKPaymentTransactionObserver alongside its StoreKit 2 listeners and records .purchased transactions from either API.Two details specific to the StoreKit 1 path: on an initial purchase StoreKit 1 leaves original empty, so SpendOwl falls back to the transaction’s own identifier to keep both paths joinable; and .restored transactions are deliberately not recorded, since a restore re-delivers a purchase that is already attributed and would otherwise double-count.
Auto-renewal transactions are tracked just like initial purchases. Each renewal adds to the user’s LTV.
Yes. Apple notifies our servers of the refund and it is recorded as negative revenue, reducing the ROAS for the associated campaign. The SDK plays no part in this — you don’t need to report refunds from the app.
By design. The SDK only sends the attribution-to-transaction link; price, currency, product type and refund state are resolved server-side from App Store Server Notifications. See What the SDK Sends.

Next Steps

Debugging

Enable logging and troubleshoot issues

Dashboard

View your ROAS data