Custom Registration Component

The Problem#

You need to register users in your own system after they complete payment through a Zellify funnel. Standard components don't support calling your backend API with custom logic. You want to send payment and user data to your backend, receive credentials (like a token), and then route the user to your app.


How It Works#

  1. The end-user goes through the Zellify funnel and lands on the paywall page
  2. They select a product or click buy, triggering checkout initiation
  3. Zellify backend creates the Stripe/Paddle customer and checkout session (with app_user_id in metadata)
  4. The checkout form renders; the user fills it out and submits payment
  5. On payment success, the user is redirected to the success page containing a custom registration component
  6. The component reads Zellify data (email, Stripe customer ID, checkout session ID, app_user_id) and calls your backend API
  7. Your backend creates the user account, links it to the payment, and returns credentials
  8. The component redirects the user to your app with the credentials

Example#

This example shows a component that sends user data to your backend after payment, receives a token, and opens your app with the credentials.

The example focuses on logic — add your own UI (button, loading state, etc.) as needed.

Code
1import { tracker } from "@tracker";2import { useVariable } from "@variables";3import { useFunnelRouter } from "@primitives";4import { useState } from "react";5
6export default function RegisterAndOpenApp() {7  const funnelRouter = useFunnelRouter();8
9  // Zellify variables (available after payment)10  const [email] = useVariable("email");11  const [stripeCustomerId] = useVariable("_stripeCustomerId");12  const [stripeCheckoutSessionId] = useVariable("_stripeCheckoutSessionId");13
14  // Session identifier (same as app_user_id in payment metadata)15  const appUserId = tracker.sessionId;16
17  const [loading, setLoading] = useState(false);18
19  const handleRegister = async () => {20    setLoading(true);21
22    // 1. Call your backend to create the user account23    const res = await fetch("https://api.yourapp.com/register", {24      method: "POST",25      headers: { "Content-Type": "application/json" },26      body: JSON.stringify({27        app_user_id: appUserId,28        email: email,29        stripe_customer_id: stripeCustomerId,30        stripe_checkout_session_id: stripeCheckoutSessionId,31      }),32    });33
34    const { token } = await res.json();35
36    // 2. Redirect to your app with credentials37    const params = new URLSearchParams({38      token: token,39      user_id: appUserId,40    });41
42    funnelRouter.openExternal(`https://yourapp.onelink.me/abc?${params}`);43  };44
45  return (46    <button type="button" onClick={handleRegister} disabled={loading}>47      {loading ? "Setting up..." : "Open App"}48    </button>49  );50}
Page refresh support: If users are expected to be refreshing success page where this component is placed (rather than always arriving via redirect after payment), tracker.sessionId may be undefined on first render — it takes up to ~200ms to initialize.Use this hook instead of reading tracker.sessionId directly:`import { useState, useEffect } from "react"; import { tracker } from "@tracker"; function useAppUserId() { const [id, setId] = useState(tracker.sessionId); useEffect(() => { if (id) return; const interval = setInterval(() => { if (tracker.sessionId) { setId(tracker.sessionId); clearInterval(interval); } }, 100); return () => clearInterval(interval); }, [id]); return id; } Then replace const appUserId = tracker.sessionId; with:const appUserId = useAppUserId(); `

What Zellify Provides#

Your custom component has access to:

ImportWhat it provides
useVariable("email")User's email address
useVariable("_stripeCustomerId")Stripe Customer ID (requires Stripe payment provider)
useVariable("_stripeCheckoutSessionId")Stripe Checkout Session ID (requires Stripe payment provider)
useVariable("_paddleCustomerId")Paddle Customer ID (requires Paddle payment provider)
useVariable("_paddleTransactionId")Paddle Transaction ID (requires Paddle payment provider)
tracker.sessionIdUnique session ID (same as app_user_id in payment metadata)
funnelRouter.openExternal(url)Navigate to external URL

What You Need to Set Up#

1. Build your backend endpoint#

Your API endpoint should:

  • Accept POST requests with the user data
  • Create or update the user account
  • Link the account to the Stripe/Paddle customer using app_user_id
  • Return credentials (token, user ID, etc.) for your app
  • Handle CORS if your funnel domain differs from your API domain

2. Create the custom component in Zellify#

In the funnel builder, create a custom component where you implement your registration logic (similar to the example above). Then drag and drop that component onto your success page — any page after the paywall where paid users land.

3. Handle the redirect#

Use funnelRouter.openExternal() to send the user to your app with the credentials. For mobile apps, this is typically a deep link or universal link (AppsFlyer, Adjust, Branch, etc.).


The Linking Key#

app_user_id (from tracker.sessionId) is the same identifier that Zellify sets in Stripe/Paddle metadata at checkout. Your backend can use this to match the registration request to the payment record.