Mobile App with Custom Backend
The Problem#
You have a mobile app with your own backend that manages user accounts and entitlements. You do not use RevenueCat. You want to acquire users through a Zellify web funnel, collect payment via Stripe or Paddle, and then get those users into your app with the correct access.
How It Works#
- The end-user goes through your Zellify funnel and reaches the paywall
- They initiate checkout. Zellify creates the customer and checkout session in your payment provider with the user's email and
app_user_idin metadata (Stripe) or custom data (Paddle) - The checkout form renders. The user fills it out and submits payment
- Stripe or Paddle sends a webhook to your backend with the transaction details, including
app_user_idin the metadata - Your backend creates the user account and grants entitlements, storing
app_user_idas the identifier - The end-user lands on your success page, which contains a Deep Link Button configured with the
app_user_id - The end-user taps the button, is routed to your app, and the app reads
app_user_idfrom the deeplink - Your app calls your backend with
app_user_id— the user's account and entitlements are already there
What Zellify Handles#
- Generates a unique
app_user_idfor every funnel visitor - Creates a Stripe Customer or Paddle Customer with the user's email at checkout
- Sets
app_user_idin Stripe Customer/Subscription metadata or Paddle Customer/Transaction custom data - Provides the Deep Link Button component on the success page, which interpolates
app_user_idinto the deeplink URL
What You Need to Set Up#
1. Register a webhook endpoint in your payment provider#
You need your own webhook endpoint that listens for payment events from Stripe or Paddle. This is how your backend learns that a purchase happened and who made it.
If you use Stripe:
Register a webhook endpoint in your Stripe Dashboard under Developers → Webhooks. The webhook events you need depend on what products you sell through Zellify:
| Scenario | Webhook Event | Why |
|---|---|---|
| One-time product | checkout.session.completed | Payment collected at checkout |
| Subscription (first payment) | checkout.session.completed | Payment collected at checkout |
| Subscription (renewals) | invoice.paid | No checkout session on renewals |
| Subscription schedule | invoice.paid | Invoice is finalized and charged synchronously after schedule creation |
| Upsells (any type) | invoice.paid | Upsells use the saved payment method to create invoices directly, without a new checkout session |
checkout.session.completed and invoice.paid. Use idempotency (track processed app_user_id values) to avoid creating duplicate accounts when multiple events fire for the same purchase.The Stripe Customer object contains the user's email. The Customer and Subscription metadata will contain app_user_id.
Refer to: Stripe: Using Webhooks with Subscriptions
If you use Paddle:
Register a webhook endpoint in your Paddle Dashboard (or note that Zellify already registers one for analytics — you need your own for entitlement logic). The recommended event for granting entitlements is transaction.completed. You can also listen for subscription.activated for subscription lifecycle events.
The Paddle Customer object contains the user's email. The Customer and Transaction custom data will contain app_user_id.
Refer to: Paddle: Webhooks Overview
2. Handle the webhook in your backend#
When the payment webhook fires, your backend should:
- Extract
app_user_idfrom the subscription/transaction metadata or custom data - Extract the customer's email from the Customer object (Stripe) or customer data in the event (Paddle)
- Create the user account (or update an existing one)
- Grant the appropriate entitlements based on the purchased product
The app_user_id becomes the key you use to identify this user across systems.
3. Configure the Deep Link Button on your success page#
Add a Deep Link Button component to the page that appears after your paywall. In the component's configuration panel, set the URL to your deeplink scheme and enable app_user_id interpolation.
If you use a deferred deeplink provider (Adjust, AppsFlyer, Branch, etc.), configure the URL according to that provider's format. The app_user_id parameter will be interpolated automatically.
4. Read app_user_id in your app#
When your app opens from the deeplink, extract the app_user_id parameter and use it to authenticate or identify the user against your backend. Your backend already has the account and entitlements ready from the webhook in step 2.
The Linking Key#
The flow depends on app_user_id appearing in two places:
- Stripe/Paddle metadata — set by Zellify at checkout in the subscription/transaction metadata (Stripe) or custom data (Paddle), included in the webhook payload your backend receives
- Deep Link Button URL — interpolated by Zellify on the success page, carried into your app via the deeplink
The customer's email is stored on the Customer object itself (not in metadata), which is created by Zellify at checkout.
Your backend stores app_user_id when processing the payment webhook. Your app sends app_user_id when requesting the user's account. The match is automatic.
Optional: Registration Webhook for Early Data#
If you want to receive the user's email and quiz answers before payment (for example, to pre-create an account or for abandoned cart recovery), you can also configure the Registration Webhook. This fires when the user submits their email in the funnel, before they reach the paywall.
The Registration Webhook payload includes the same app_user_id that will later appear in Stripe/Paddle metadata, so you can link pre-payment and post-payment data.
This is entirely optional. The payment webhook from Stripe or Paddle contains everything you need (app_user_id + email) to create the account and grant entitlements. For the full pattern, see Early Account Creation.