App integration

Use this guide when your funnel sells access on the web and the customer should receive that access inside your iOS or Android app after install.

Recommended architecture

web2app keeps payment and access simple:

  1. Stripe or Paddle confirms the payment by webhook.
  2. web2app stores a purchase token and 5-character access code.
  3. The Success step sends the customer through AppsFlyer OneLink, QR, email fallback, or manual code.
  4. Your app receives deep_link_sub1 as purchaseToken and deep_link_sub2 as accessCode.
  5. Your app or app backend calls web2app's app access API.

AppsFlyer is used for routing, attribution, and deferred deep linking. Stripe/Paddle remain the payment source of truth. web2app exposes the simple access API your app needs.

Choose an access mode

Configure this in Funnel settings → Web-to-App Access.

ModeUse whenWho grants app access
Managed AccessYou want the simplest integration.web2app
Webhook RelayYou already have your own backend/account system.Your backend
Attribution OnlyYou only want AppsFlyer links/analytics.Outside web2app

For most apps, start with Managed Access.

Workspace setup

In Workspace Settings:

  1. Connect Stripe or Paddle.
  2. Optional: enter AppsFlyer Dev Key + App ID and enable S2S events.
  3. Optional but recommended: set an App Access API Key and enable "Require this key".

Sensitive values are stored encrypted, the same way as Stripe and Paddle secrets.

If you run the product on your own infrastructure, turn on Managed Access only after whoever maintains that environment has completed the one-time database setup required for web-to-app access (they receive this as part of the self-hosting handoff — not something you configure in the funnel UI).

When the App Access API Key is enabled, send:

Authorization: Bearer YOUR_APP_ACCESS_API_KEY

Use this key from your app backend when possible. If your mobile app calls web2app directly, treat the key as a friction layer, not as a perfect mobile secret.

Funnel setup

In Funnel settings → Web-to-App Access:

  1. Select Managed Access.
  2. Enable AppsFlyer OneLink and enter your OneLink base URL.
  3. Keep pid as web2app unless you have a naming convention.
  4. Add App Store / Google Play fallback URLs.
  5. Enable access code, optional Managed Access email, and fallback links.
  6. Add Universal Links / Android App Links values if you use a custom domain.

Generated OneLink parameters:

pid=web2app
c={utm_campaign or funnel slug}
af_siteid={funnel_id}
deep_link_value=purchase_success
deep_link_sub1={purchaseToken}
deep_link_sub2={accessCode}
deep_link_sub3={planId}
deep_link_sub4={workspaceId}
deep_link_sub5={funnelSlug}

AppsFlyer's Unified Deep Linking privacy rules return deep_link_value and deep_link_sub1-10 for new users, which is why web2app stores the purchase token/code in those fields. See AppsFlyer iOS UDL, Android UDL, and OneLink campaign links.

App implementation

On first open after install:

  1. Initialize the AppsFlyer SDK.
  2. Read the Unified Deep Linking result.
  3. If deep_link_value === "purchase_success", extract:
    • deep_link_sub1purchaseToken
    • deep_link_sub2accessCode
  4. Call POST /api/app/claim-purchase.

Claim request:

POST https://YOUR_FUNNEL_DOMAIN/api/app/claim-purchase
Authorization: Bearer YOUR_APP_ACCESS_API_KEY
Content-Type: application/json
{
  "purchaseToken": "pur_abc123",
  "accessCode": "K7MZQ",
  "appUserId": "user_123",
  "platform": "ios",
  "appsFlyerId": "1700000000000-123456789"
}

Success response:

{
  "ok": true,
  "purchaseToken": "pur_abc123",
  "accessCode": "K7MZQ",
  "entitlement": {
    "status": "active",
    "planId": "monthly",
    "expiresAt": "2026-05-30T00:00:00.000Z"
  }
}

Denied response:

{
  "ok": false,
  "reason": "pending | past_due | canceled | refunded | already_claimed | not_found | missing_token | access_not_managed | unauthorized | internal_error"
}

Unexpected server errors return HTTP 500 with reason: "internal_error" and error: "Internal server error".

Check access later:

GET https://YOUR_FUNNEL_DOMAIN/api/app/access?workspaceId=WORKSPACE_ID&appUserId=user_123
Authorization: Bearer YOUR_APP_ACCESS_API_KEY
{
  "status": "active",
  "planId": "monthly",
  "expiresAt": "2026-05-30T00:00:00.000Z"
}

Optional app event:

POST /api/app/events
Authorization: Bearer YOUR_APP_ACCESS_API_KEY
Content-Type: application/json
{
  "event": "first_open_after_purchase",
  "purchaseToken": "pur_abc123",
  "appUserId": "user_123",
  "appsFlyerId": "1700000000000-123456789"
}

Universal Links and Android App Links

If your funnel uses a custom domain, web2app can publish:

https://YOUR_FUNNEL_DOMAIN/.well-known/apple-app-site-association
https://YOUR_FUNNEL_DOMAIN/.well-known/assetlinks.json

In Funnel Settings, enter:

  • Apple Team ID
  • iOS Bundle ID
  • Android package name
  • Android SHA-256 release certificate fingerprint

Apple requires a matching Associated Domains entitlement for each domain serving apple-app-site-association. Android requires assetlinks.json at the well-known location and a matching app manifest intent filter with android:autoVerify="true". See Apple Supporting associated domains, Apple Supporting universal links, and Android Configure website associations.

web2app publishes iOS paths for /open/* and /dl/* only. This keeps normal funnel, checkout, and legal pages on the web instead of letting the app intercept the whole custom domain. On Android, keep the same path restrictions in your app manifest intent filters.

Email and fallback access

web2app can show the access code, QR code, and store fallback links on the Success step.

For Managed Access, Workspace Settings can send a simple transactional access email through Resend or SendGrid after Stripe/Paddle confirms the purchase. Per-funnel Success delivery controls whether that funnel uses the email fallback.

For Webhook Relay, handle purchase.completed in your own backend or automation tool and send mail from your email provider. This keeps copy, templates, consent, unsubscribe handling, sender reputation, localization, retries, and customer account logic under your control.

For Attribution Only and Webhook Relay, web2app does not grant app entitlements through claim-purchase the way Managed Access does; your app or backend owns access (see Webhook Relay guide).

Testing checklist

  1. Use Stripe test mode or Paddle sandbox.
  2. Complete a real checkout flow, not just preview.
  3. Confirm Success URL contains _w2a_purchase_token and _w2a_code.
  4. Open the OneLink on a real mobile device. AppsFlyer notes that pasting links into browser/address bars or notes can fail to trigger Universal/App Links reliably.
  5. Install and open the app.
  6. Confirm AppsFlyer UDL returns deep_link_sub1/sub2.
  7. Call claim-purchase.
  8. Cancel/refund in Stripe/Paddle and confirm GET /api/app/access becomes denied or past due according to your product rules.

Compliance note

Selling digital goods or subscriptions on the web and unlocking them in your app can depend on app category, country or region, and whether you participate in the right Apple or Google programs. Read the official guidance before you launch: