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 callconfigure():
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
- 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 toTransaction.updates - 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
- The transaction identifiers are extracted — see What the SDK Sends
- The attribution-to-transaction link is sent to SpendOwl servers
- Revenue for that transaction is resolved server-side from Apple’s App Store Server Notifications
- 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’sInfo.plist:
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.
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:- User installs from an Apple Search Ads campaign
- Attribution is captured by SpendOwl
- User makes a purchase (days or weeks later)
- Revenue is linked to the original campaign
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.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:FAQ
Do I need to call any method after a purchase?
Do I need to call any method after a purchase?
No. SpendOwl observes transactions automatically. Just ensure the SDK is configured before purchases occur.
Some of my consumable purchases are missing. Why?
Some of my consumable purchases are missing. Why?
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.Are test purchases tracked?
Are test purchases tracked?
Sandbox purchases are tracked separately and don’t affect production ROAS. Use a test API key in development.
What about StoreKit 1?
What about StoreKit 1?
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.How are renewals handled?
How are renewals handled?
Auto-renewal transactions are tracked just like initial purchases. Each renewal adds to the user’s LTV.
Are refunds tracked?
Are refunds tracked?
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.
Why don't my purchase events contain a price?
Why don't my purchase events contain a price?
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