Skip to main content

Funnel Pages

A funnel is a sequence of pages (steps). Each page has a type that determines its capabilities — whether it can accept payments, show upsells, fire conversion events, etc.

Page Types

Every node in a funnel has a type. Here are all the available types and what they unlock:
TypePurposePayment FormOrder BumpsUpsell OffersCan be EntryConversion
checkoutPayment pageYesYesYes
thankyouOrder confirmationYes
offerUpsell / downsell / cross-sellOptionalYes
landingLanding page / hero with CTAYes
customAny custom pageOptionalOptionalOptionalYes
errorError state page
externalRedirect to external URLYes
storefront_shopifyShopify product pageYes
storefront_woocommerceWooCommerce product pageYes

Why types matter

The step type controls what stepConfig options are available:
  • checkout steps can have paymentSetupConfig (card, Apple Pay, Google Pay routing), orderBumps, and payment config
  • offer steps can have upsellOffers for post-purchase offers
  • thankyou steps are marked as the conversion endpoint — analytics events like Purchase fire here
  • landing and custom steps are general-purpose — they have pixels and scripts but no payment-specific config
// Checkout step — full payment config
{
  id: 'step_checkout',
  type: 'checkout',
  config: {
    stepConfig: {
      paymentSetupConfig: { card: { enabled: true, paymentFlowId: 'flow_xxx' } },
      orderBumps: { mode: 'custom', enabledOfferIds: ['offer_1'] },
      pixels: { facebook: [...] },
    },
  },
}

// Offer step — upsell config
{
  id: 'step_upsell',
  type: 'offer',
  config: {
    stepConfig: {
      upsellOffers: [{ offerId: 'offer_2', type: 'one_click' }],
      pixels: { facebook: [...] },
    },
  },
}

// Landing step — pixels and scripts only
{
  id: 'step_landing',
  type: 'landing',
  isEntry: true,
  config: {
    stepConfig: {
      pixels: { facebook: [...] },
      scripts: [{ name: 'Hotjar', content: '...', position: 'head-end', enabled: true }],
    },
  },
}

Common funnel patterns

Landing → Checkout → Thank You                    (simple)
Landing → Checkout → Offer → Thank You            (with upsell)
Storefront (Shopify) → Checkout → Thank You       (Shopify integration)
Checkout → Offer (EU) / Offer (US) → Thank You    (geo-conditional)
Landing → Checkout → Offer → Offer → Thank You    (multi-offer)
You can combine any types in any order. The only requirement is that a funnel with payments needs at least one checkout step, and a complete flow should end with a thankyou step for conversion tracking.

Three Approaches

TagadaPay offers three ways to create funnel pages, depending on how much control you need:
Native CheckoutCustom HTMLPlugin SDK
SetupZero code — auto-injectedDeploy raw HTML/CSS/JSBuild a React or JS app
Page typescheckout + thankyou onlyAny typeAny type
ControlTagadaPay handles the UIFull control, no frameworkFull control + SDK hooks
Best forGetting live fastQuick prototypes, simple pagesProduction-grade branded experiences
PaymentsAutomaticYou wire up tokenizationSDK handles tokenization, 3DS, payments
FunnelsAutomatic navigationManual routingSDK handles step navigation
Deployment2 steps4 steps (inline deploy via API)CLI: tgd deploy from your repo
A/B testingNoVia API split configVia API or CLI
When you create a funnel with checkout and thankyou step types, TagadaPay automatically injects its built-in checkout UI. No plugin deployment, no HTML — just define the flow.

Create and activate

import Tagada from '@tagadapay/node-sdk';
const tagada = new Tagada('your-api-key');

// Create funnel — native checkout is auto-injected
const funnel = await tagada.funnels.create({
  storeId: 'store_...',
  config: {
    id: 'my-checkout',
    name: 'My Checkout',
    version: '1.0.0',
    nodes: [
      {
        id: 'step_checkout',
        name: 'Checkout',
        type: 'checkout',
        kind: 'step',
        isEntry: true,
        position: { x: 0, y: 0 },
        config: { path: '/checkout' },
      },
      {
        id: 'step_thankyou',
        name: 'Thank You',
        type: 'thankyou',
        kind: 'step',
        position: { x: 300, y: 0 },
        config: { path: '/thankyou' },
      },
    ],
    edges: [
      { id: 'e1', source: 'step_checkout', target: 'step_thankyou' },
    ],
  },
  isDefault: true,
});

// Activate — triggers routing, mounts pages to CDN
const result = await tagada.funnels.update(funnel.id, {
  storeId: 'store_...',
  config: funnel.config,
});

const funnelCheckoutUrl = result.funnel.config.nodes
  .find(n => n.id === 'step_checkout').config.url;
console.log('Funnel live at:', funnelCheckoutUrl);

// Generate a checkout link with a pre-loaded cart
const session = await tagada.checkout.createSession({
  storeId: 'store_...',
  items: [{ variantId: 'variant_...', quantity: 1 }],
  currency: 'USD',
  checkoutUrl: funnelCheckoutUrl,
});
console.log('Checkout link:', session.redirectUrl);
No pluginId or instanceId needed. TagadaPay detects that checkout and thankyou nodes have no plugin assigned and injects the native checkout automatically.The checkoutUrl parameter in createSession is critical — it tells TagadaPay to redirect customers to your funnel’s deployed page instead of the default CMS URL.

What you get

The native checkout includes:
  • Card payment form with real-time validation
  • Order summary with product details and pricing
  • Multi-currency support based on your store config
  • Mobile-responsive layout
  • Built-in thank you page with order confirmation
For the full setup including processor, store, and product creation, see the Merchant Quick Start.

Updating a Live Plugin

To update your custom checkout without downtime, deploy a new version with the same plugin name:
const updated = await tagada.plugins.deploy({
  storeId: 'store_...',
  manifest: {
    name: 'my-custom-checkout',
    version: '1.1.0',
    mode: 'spa-mode',
    pages: [
      { id: 'checkout', path: '/checkout', name: 'Checkout', isDefault: true },
      { id: 'thankyou', path: '/thankyou', name: 'Thank You' },
    ],
  },
  inlineAssets: [/* updated HTML/CSS/JS */],
  autoInstantiate: { stepType: 'checkout', isPreview: false },
});

// Point the funnel to the new instance
const config = JSON.parse(JSON.stringify(funnel.config));
for (const node of config.nodes) {
  node.config.instanceId = updated.instanceId;
  node.config.instanceVersion = '1.1.0';
}

await tagada.funnels.update(funnel.id, {
  storeId: 'store_...',
  config,
});

A/B Testing

Split traffic between two checkout designs:
const variantB = await tagada.plugins.deploy({
  storeId: 'store_...',
  manifest: {
    name: 'checkout-variant-b',
    version: '1.0.0',
    mode: 'spa-mode',
    pages: [
      { id: 'checkout', path: '/checkout', name: 'Checkout B', isDefault: true },
    ],
  },
  inlineAssets: [/* variant B HTML */],
  autoInstantiate: { stepType: 'checkout', isPreview: false },
});

// Get routeId from the activated funnel
const checkoutNode = result.funnel.config.nodes
  .find(n => n.id === 'step_checkout');
const routeId = checkoutNode.config.routeId;

// 50/50 split
await tagada.plugins.configureSplit({
  routeId,
  storeId: 'store_...',
  splitConfig: {
    type: 'weighted',
    instances: [
      { instanceId: deployment.instanceId, deploymentId: deployment.deploymentId, weight: 50 },
      { instanceId: variantB.instanceId, deploymentId: variantB.deploymentId, weight: 50 },
    ],
    stickyDuration: 3600,
  },
});

Direct Mounting (Without Funnel)

For standalone pages (landing pages, portals) outside of a checkout flow:
await tagada.plugins.mount({
  deploymentId,
  deploymentInstanceId: instanceId,
  storeId: 'store_...',
  alias: 'my-landing-page',
  matcher: '.*',
  force: true,
});
// → my-landing-page--store_xxx.cdn.tagadapay.com

Custom Domains

By default, pages are served on *.cdn.tagadapay.com. To use your own domain (e.g. checkout.yourstore.com), use tagada.domains.

Step 1 — Register the domain

const { domain } = await tagada.domains.add({
  domain: 'checkout.yourstore.com',
});

console.log(domain.id);               // cdm_abc123
console.log(domain.isVerified);        // false
console.log(domain.verificationToken); // vrf_token_xyz

Step 2 — Configure DNS

Add a CNAME record at your registrar:
TypeNameValue
CNAMEcheckoutcname.vercel-dns.com
Use tagada.domains.getConfig() and tagada.domains.getDnsLookup() to check DNS propagation programmatically before verifying.
const config = await tagada.domains.getConfig({
  domain: 'checkout.yourstore.com',
});
console.log(config.misconfigured);     // true (DNS not propagated yet)
console.log(config.recommendedCNAME);  // cname.vercel-dns.com

const dns = await tagada.domains.getDnsLookup({
  domain: 'checkout.yourstore.com',
  recordType: 'CNAME',
});
console.log(dns.records); // ['cname.vercel-dns.com'] when ready

Step 3 — Verify

Once DNS has propagated:
const result = await tagada.domains.verify({
  domainId: domain.id,
});

console.log(result.status);          // 'Valid Configuration'
console.log(result.ownershipStatus); // 'Verified'
Possible status values: Valid Configuration, Invalid Configuration, Pending Verification, Domain Not Found, Error.

Step 4 — Mount on the custom domain

Use customDomain instead of hostname or alias when mounting:
await tagada.plugins.mount({
  deploymentId,
  deploymentInstanceId: instanceId,
  storeId: 'store_...',
  customDomain: 'checkout.yourstore.com',
  basePath: '/',
  matcher: '/',
});
// → https://checkout.yourstore.com/
HTTPS is provisioned automatically. A/B testing and sticky sessions work identically on custom domains.

Managing domains

const { domains } = await tagada.domains.list();

await tagada.domains.remove({ domainId: 'cdm_abc123' });

Promote Staging to Production

After testing on the staging CDN URL (*.cdn.tagadapay.com), promote the funnel to your custom domain in one call. The server handles instance cloning, route setup, and prebuilds automatically.
const result = await tagada.funnels.promote(funnel.id, {
  storeId: 'store_...',
  customDomain: 'checkout.yourstore.com',
});

console.log(result.success); // true
console.log(result.message); // 'Funnel promoted to checkout.yourstore.com'
The domain must be verified before promoting. If the domain is already in use by another funnel, pass acknowledgeConflict: true to override:
await tagada.funnels.promote(funnel.id, {
  storeId: 'store_...',
  customDomain: 'checkout.yourstore.com',
  acknowledgeConflict: true,
});

Full workflow: staging to production

import Tagada from '@tagadapay/node-sdk';
const tagada = new Tagada('your-api-key');

// 1. Deploy + create funnel (see sections above)
// Your funnel is now live on *.cdn.tagadapay.com (staging)

// 2. Register and verify your domain
const { domain } = await tagada.domains.add({ domain: 'checkout.yourstore.com' });
// ... configure CNAME, wait for DNS ...
await tagada.domains.verify({ domainId: domain.id });

// 3. Promote — one call moves everything to production
await tagada.funnels.promote(funnel.id, {
  storeId: 'store_...',
  customDomain: 'checkout.yourstore.com',
});

// 4. Create checkout sessions pointing to the production URL
const session = await tagada.checkout.createSession({
  storeId: 'store_...',
  items: [{ variantId: 'variant_...', quantity: 1 }],
  currency: 'USD',
  checkoutUrl: 'https://checkout.yourstore.com/checkout',
});
console.log('Live checkout:', session.redirectUrl);

Plugin Management

const plugins = await tagada.plugins.list('store_...');
const instances = await tagada.plugins.listInstances('store_...');
const details = await tagada.plugins.retrieveDeployment(deploymentId);
await tagada.plugins.del(pluginId, 'store_...');

Next Steps

Plugin SDK Reference

Full SDK documentation — hooks, API, examples

Plugin CLI

Deploy plugins from your terminal with tgd deploy

Merchant Quick Start

Full 7-step setup from processor to live checkout

Host & A/B Test

Custom domains, A/B testing, and hosting via REST API