Skip to content

Connect Your App ​

The web funnel sells the subscription. Your app gives the buyer access when they sign in with the same email they entered in the funnel. A verified email is the whole link between the purchase and the app account, so no click attribution or special links are needed.

text
Visitor completes the funnel and checkout with their email
  -> installs and opens your app
  -> signs in with that email and verifies it (OTP or equivalent)
  -> your backend verifies the subscription by that email
  -> tracks the login and saves the returned user_id on the account
  -> later app opens verify access again with the saved user_id

Tell buyers on the funnel's success screen to sign in to the app with the email they just used. That instruction does most of the work.

Before You Start ​

You need:

  • the project SDK publishable key from Project Settings;
  • an OTP or equivalent email-verification flow in your app;
  • a backend, Firebase custom claim, or equivalent place to store one funnelsgrove user_id on the authenticated account.

The two identity requests below send:

http
X-SDK-Publishable-Key: <project-sdk-publishable-key>

The publishable key identifies the project. It is safe to distribute, but it is not proof of user identity.

Call the two identity endpoints from your authenticated backend. Verify the OTP result there, call funnelsgrove, and return only the entitlement fields your app needs. This keeps your integration from trusting raw client input or accepting an unverified app-account association.

Three Rules ​

  1. Verify email ownership before lookup. The API cannot tell whether your app completed OTP.
  2. Keep verification and login tracking separate. verify_subscription is read-only. track_login durably records registration for an existing user, independently of subscription status.
  3. Save the exact user.user_id. Treat it as opaque: do not generate it, remove its u_ prefix, or replace it with another ID.

The Sequence ​

  1. The user signs in and completes OTP or equivalent verification.
  2. Verify the subscription with that confirmed email.
  3. If user is not null, track the login with the returned user.user_id.
  4. Save that ID on the authenticated app account.
  5. On later app opens, verify access again with the saved ID.

When user is null, ask the user to sign in with the email they used in the funnel. A resolved user can be tracked even when active is false. Unlock paid production content only when active is true and test is false.

The Two Requests ​

Verify Subscription ​

Pass either a known funnelsgrove userId or an email whose ownership has already been verified.

By email:

http
POST https://sdk-api.funnelsgrove.com/sdk/public/users/verify_subscription
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "email": "verified-user@example.com"
}

By saved user ID:

http
POST https://sdk-api.funnelsgrove.com/sdk/public/users/verify_subscription
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "userId": "u_123"
}

The response contains the access flag, a test flag, and the stable public user ID. It does not expose the email, internal database ID, or subscription records. When the active subscription was purchased in Stripe test mode, test is true; never grant production access from a test subscription.

Example responses

Active access:

json
{
  "active": true,
  "test": false,
  "user": {
    "user_id": "u_123"
  }
}

No matching user:

json
{
  "active": false,
  "test": false,
  "user": null
}

Known user without active access:

json
{
  "active": false,
  "test": false,
  "user": {
    "user_id": "u_123"
  }
}

This request changes nothing and returns Cache-Control: no-store. Re-check at each entitlement boundary instead of caching active on the device.

Track Login ​

Call this after OTP succeeds and the verified identity resolves to a user. Track registration/login regardless of paid access; use subscription verification separately to decide whether to unlock content.

http
POST https://sdk-api.funnelsgrove.com/sdk/public/users/track_login
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "userId": "u_123",
  "runtimeEnvironment": "test"
}

runtimeEnvironment is optional. Set it to live or test to check only that environment. If omitted, track_login looks for a live user first and falls back to test only when the live user is absent. Subscriptions are not read.

The compact response contains only registration acceptance and the user identifier:

json
{
  "user": { "user_id": "u_123" },
  "login": {
    "tracked": true,
    "newlyTracked": true,
    "trackedAt": "2026-09-09T12:00:00.000Z"
  }
}

Use the separate verification response for paid access. Registration tracking is also valid for users without a subscription.

login.tracked: true means success. login.newlyTracked is true on the first call and false on an idempotent replay. Treat both as success.

Persist the exact returned ID on your backend:

ts
await firebaseAdmin.auth().setCustomUserClaims(firebaseUid, {
  funnelsGroveUserId: trackLoginResponse.user.user_id,
});

Optional: Prefill The Email With AppsFlyer ​

If you already use AppsFlyer, you can save buyers from retyping their email:

  1. Point the funnel's app download button at an AppsFlyer OneLink and add the buyer's email as a deep-link parameter, for example deep_link_sub1.
  2. On first launch, read the parameter with AppsFlyer's Unified Deep Linking (deferred deep linking also works after a fresh install).
  3. Use the value only to prefill the sign-in field. The user still verifies the email; never look up a subscription or grant access from the link value.

The email then travels through AppsFlyer, so check that this fits your privacy policy. The flow works the same without AppsFlyer: the user types the email.

Later App Opens ​

Call verify_subscription with the ID saved on the authenticated account:

http
POST https://sdk-api.funnelsgrove.com/sdk/public/users/verify_subscription
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "userId": "u_123"
}

Grant paid production access only while active is true and test is false.

Responses And Retries ​

Verification reports access; login tracking reports registration.

verify_subscription ​

ResponseMeaningWhat to do
200, active: trueActive accessContinue
200, active: falseNo active accessDo not grant access
400Identity or publishable key is missingFix the request
401Invalid publishable keyFix the key
503, SDK_DEPENDENCY_UNAVAILABLEA subscription read dependency is temporarily unavailableWait for Retry-After, then retry the same request
500Unclassified server errorRetry with bounded backoff

Immediately after checkout, billing webhook processing may briefly produce active: false. Retry with bounded backoff before deciding that access is inactive.

track_login ​

ResponseMeaningWhat to do
200, login.tracked: trueLogin recordedSave user.user_id
400userId or publishable key is missingFix the request
401Invalid publishable keyFix the key
404, Funnel user not foundWrong user ID or projectRe-resolve with verify_subscription; do not retry the same ID
503, SDK_DEPENDENCY_UNAVAILABLETemporary dependency failureRespect retryable and Retry-After; do not automatically replay a non-retryable write.
500Unclassified errorPreserve the uncertain state; do not assume registration failed.

funnelsgrove records registration_completed on the first successful login-tracking request. A later explicit login reuses that event identity. For automatic retries, follow the operation-specific rules, including retryable: false after a write may have started.

Testing preview or test-mode purchases

A native request without a preview Origin checks live users and subscriptions. To test a preview purchase, call through your backend with that funnel's preview origin. Otherwise a preview-only user can return active: false even when its test subscription is active.

Verification Checklist ​

  • OTP completes before any subscription lookup by email.
  • Identity calls run through the authenticated backend; the app does not relay the full login response.
  • Lookup uses the verified account email, not raw form input.
  • Any prefilled email is verified before lookup.
  • Login tracking runs after verified ownership when a user resolves, regardless of paid access.
  • The app stores the exact returned user.user_id.
  • On later app opens, verify with the stored ID and do not cache active.
  • 200 with active: false is handled as no access.
  • 409 is never treated as success.

For endpoint details, see the Public API.

Build. Launch. Grow.