Store-Direct Validation
Validate purchases directly against Apple and Google — no RevenueCat needed
What is store-direct?
InfluTo supports exactly two purchase-validation paths — pick one, never both:
- RevenueCat webhook — if your app already uses RevenueCat, use the RevenueCat integration instead.
- Store-direct (this page) — your app uses native StoreKit 2 (iOS) and/or Play Billing (Android) with no subscription platform. The SDK sends the store proof to InfluTo, and our servers validate it directly with Apple / Google. No backend of your own is required.
What InfluTo handles server-side
- ✓ Purchase validation against the App Store Server API / Google Play Developer API — your app is never the source of truth
- ✓ Renewals, cancellations and expirations: every attributed subscription is re-checked with the store hourly, even if the user never reopens the app
- ✓ Refunds and revocations (subscriptions and one-time products): detected server-side and the commission is clawed back automatically
- ✓ One-time products: each purchase creates its own conversion and commission
Step 1: Dashboard credentials (one-time)
In the dashboard: select your app → Settings → Validation tab → choose "Apple — direct", "Google — direct" or"Both stores — direct". The credential card for each store appears once selected.
Google Play (service-account JSON)
- In Google Cloud Console, create (or pick) a project and create a service account (IAM & Admin → Service Accounts). No project roles are needed.
- On the service account, create a JSON key (Keys → Add key → JSON) and download it.
- In the same Cloud project, enable the Google Play Android Developer API (APIs & Services → Library).
- In Play Console → Users and permissions, invite the service account's email (the
...@...iam.gserviceaccount.comaddress) with view app information and view financial data permissions. - Drop the JSON file into the Google Play card in the Validation tab and click Save & validate.
⚠️ Google can take up to ~48 hours to propagate freshly granted Play Console access — a "permission" error right after inviting the service account usually just means wait and re-run the check.
Apple (App Store Server API)
- In App Store Connect → Users and Access → Integrations → In-App Purchase, generate an API key.
- Note the Issuer ID and the key's Key ID, and download the .p8 private key file.
- Enter all three in the Apple card of the Validation tab, together with your app's numeric App Store ID, and click Save & validate.
Until the credentials check is green, /sdk/purchase returns 400 — the SDK ships safely dormant, nothing double-fires when you flip the switch later.
Step 2: The app side — usually nothing
For store-direct apps, auto-capture is ON by default: once /sdk/init returns store_direct: true (i.e. your credentials are green), initialize() starts observing purchases, back-syncs unfinished ones, sends each to /sdk/purchase, and dedups so a purchase is reported exactly once. Auto-capture never runs for RevenueCat apps, so it can't double-report.
// Whole integration for a store-direct app:
await InfluTo.initialize({ apiKey: 'YOUR_API_KEY' });
// ...nothing else to call at purchase time.Per-SDK notes: React Native · iOS · Android (add to.influ:android-sdk-billing) · Flutter (add influto_iap).
Android one-time products: list your one-time product IDs in the SDK config (e.g. InfluToConfig(oneTimeProductIds = ...)) — a Play purchase object doesn't carry its product type. Keep acknowledging purchases in your own billing code as usual: InfluTo only reads from the stores, never modifies.
Manual reporting (only if you disable auto-capture)
Set autoCapture: false so exactly one path runs, then call reportPurchase() yourself:
await InfluTo.initialize({ apiKey: 'YOUR_API_KEY', autoCapture: false });
// iOS — StoreKit 2 signed transaction (JWS):
await InfluTo.reportPurchase({ platform: 'ios', signedTransaction: jwsString });
// Android subscription — purchaseToken only (OMIT productId):
await InfluTo.reportPurchase({ platform: 'android', purchaseToken: token });
// Android ONE-TIME product — MUST send productId, SHOULD send price + currency:
await InfluTo.reportPurchase({
platform: 'android', purchaseToken: token,
productId: 'coins_100', price: 4.99, currency: 'USD',
});reportPurchase()throws on failure (it does not fail-soft) — surface the error.- A 503 means the FX rate was unavailable → retry (the report is retryable and idempotent).
- Conversions dedup on the store identity (Apple
originalTransactionId/ GooglepurchaseToken) — repeated reports are idempotent.
Invariants
- Pick exactly ONE path. Do not also wire RevenueCat. If you report manually, set
autoCapture: false. Two reporting paths double-report conversions. - Android one-time products MUST send
productId; OMIT it for subscriptions.
Verify
After a sandbox purchase, check the Events log in the dashboard (each purchase shows its referral code and environment), the MCP tool influto_recent_events, or GET /api/agent/apps/{app_id}/events/recent — see Testing Integration for the auth model. The purchase appears with "attributed": true and the matching referral code. If nothing appears, the credentials check isn't green yet and /sdk/purchase is returning 400.