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 → SettingsValidation tab → choose "Apple — direct", "Google — direct" or"Both stores — direct". The credential card for each store appears once selected.

Google Play (service-account JSON)

  1. In Google Cloud Console, create (or pick) a project and create a service account (IAM & Admin → Service Accounts). No project roles are needed.
  2. On the service account, create a JSON key (Keys → Add key → JSON) and download it.
  3. In the same Cloud project, enable the Google Play Android Developer API (APIs & Services → Library).
  4. In Play Console → Users and permissions, invite the service account's email (the ...@...iam.gserviceaccount.com address) with view app information and view financial data permissions.
  5. 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)

  1. In App Store Connect → Users and Access → Integrations → In-App Purchase, generate an API key.
  2. Note the Issuer ID and the key's Key ID, and download the .p8 private key file.
  3. 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 / Google purchaseToken) — 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.