Coded Funnels
A coded funnel is a funnel whose pages you build yourself — a React app, a Next.js site, a page generated by Claude Code or Lovable — with the Headless SDK doing the checkout. You describe its steps once in a file,tagada.funnel.ts, and push it. Each step then appears as a card on the canvas of your store, next to the pages TagadaPay hosts, and you bind your pixels (Meta, TikTok, Snapchat, Google Tag Manager, Microsoft Clarity) to its events from there. Your code reports where the visitor is with one call per step; TagadaPay loads the pixels and fires them.
Three pieces work together:
Coded funnel or external step? An external step is one page of yours added to a funnel TagadaPay hosts, reported from your server or with
curl. A coded funnel is the whole journey in your code, declared by a manifest, with pixels fired in the browser by the SDK. They are two separate mechanisms and do not share cards.@tagadapay/headless-sdk1.16.0 or later (1.15.0 introducedtrackbut was never published to npm); 1.18.0 for the React provider form shown below.@tagadapay/node-sdk3.23.0 or later, as a dev dependency: it carriesdefineFunneland thetagadacommand. 3.26.0 or later reads your credentials from.envfiles and prints the pixel origins infunnel csp.- A secret API key and your store id — see Get an API key. The key is only used by the command line, never in the browser.
- Node.js 22.18 or later to load a
.tsmanifest. On an older Node, name the filetagada.funnel.mjsor writetagada.funnel.json.
1. Install
2. Declare the funnel
Createtagada.funnel.ts at the root of your project (the command looks for tagada.funnel.ts, .mjs, .js, then .json in the current directory):
defineFunnel checks the whole manifest and throws one TagadaValidationError that lists every problem with its path (steps[2].events[0].name: …). validateManifest runs the same checks and returns { ok, errors } instead of throwing.
The funnel
A step
Step types and their default events
Every step has a Page view event. The type adds the standard events a step of that kind usually has:Names and limits
key, stepidand eventname: lowercase letters, digits,-and_, starting with a letter or a digit, 64 characters max (/^[a-z0-9][a-z0-9_-]{0,63}$/).- Reserved event names, refused because TagadaPay emits them itself:
page_view,checkout_started,purchase,checkoutStarted,cartAbandoned,paid,paymentFailed,orderConfirmed, and anything starting withcheckout/,order/,payment/,subscription/orclub/. - 60 steps per funnel, 12 events per step, 20 variables per event, 20 domains, names up to 120 characters,
pathup to 512 characters.
3. Report the steps
Pass the funnelkey to the client, then call track.step() once when the visitor reaches a step.
- JavaScript
- React
track.step() does:
- It fires the Page view of every pixel bound to that step.
pathdefaults tolocation.pathname; passtrack.step('product', { path: '/products/blue-hoodie' })to report another one. - Two calls for the same step and path within one second count once, so React StrictMode and two components of the same screen calling
useStep('quiz')are safe. - Every later
track.event()belongs to this step, until the nexttrack.step().
4. Report events
Any other name is a custom event: declare it in the step’s
events, then bind it on the canvas. A custom event reaches no pixel until you bind it.
A step only offers its Page view, the default events of its type and the events it declares. To send lead from a page step, for example, add { name: 'lead' } to that step’s events; an event the step does not offer reaches no pixel.
- The properties must be flat: strings, numbers, booleans. A nested object or array is dropped, not stringified.
- An event sent before the first
track.step()of the page belongs to no step and is dropped. - Do not report
InitiateCheckoutorPurchaseyourself — see Checkout and purchase.
5. Push the manifest
.env
created, updated, unchanged or removed, then any warning.
- Pushing an unchanged manifest does nothing. Push again whenever you add, rename or remove a step.
- A step you remove from the manifest disappears from the canvas. If you add it back with the same
id, it comes back with the pixels you had bound to it. - The key and the store id come from the flags first, then from environment variables, then from
.env.local, then from.envin the current directory. Nothing is ever written to those files. npx tagada <your email>creates a sandbox account if you do not have one yet and writesTAGADA_API_KEYandTAGADA_STORE_IDto.env, sofunnel pushworks right after it.
tagada.funnels.declare(storeId, manifest), tagada.funnels.uploadStepImage(storeId, key, stepId, { base64, contentType }) and tagada.funnels.health(storeId, key?). defineFunnel, validateManifest and the CSP helpers are also exported from @tagadapay/node-sdk/edge, which has no Node.js import and runs on Cloudflare Workers, Vercel Edge and Deno.
6. Bind your pixels on the canvas
Open the canvas of the store: the steps are there as cards with an SDK badge, in the order of the manifest, wired bynext. A step nobody has reached yet carries a Never seen pill.
On the first push, TagadaPay binds the default events for you. For every pixel integration the store has enabled (Meta, TikTok, Snapchat, Google Tag Manager, Microsoft Clarity), each new step gets its Page view and the default events of its type. This happens once per step: an event you unbind stays unbound on every later push. Custom events are never bound automatically. If the store has no pixel integration yet, nothing is bound, and the first push after you connect one does it.
To change what fires, open a step card and switch pixels on or off per event. Through the API this is a bind command whose target is { "kind": "node", "nodeId": "<the card id>" } and whose event is one the step offers (PageView, a default event of its type, or one it declares — a declared lead binds as Lead). Any other command on an SDK card is refused with sdk-node-read-only, and an undeclared event with sdk-unknown-event: the step itself only changes through the manifest.
A coded funnel loads Meta, TikTok, Snapchat, Google Tag Manager and Microsoft Clarity. Pinterest is not available there.
Checkout and purchase
You never send these two yourself. Once tracking is on (after a firsttrack.step()), the SDK fires them from its own checkout and payment modules:
- InitiateCheckout when
checkout.loadSession()loads a checkout session, with the session’s amount, currency and items. - Purchase when a payment made through the SDK reaches
succeeded, directly or after a 3DS redirect. A payment that is only authorized is not a purchase and fires nothing.
InitiateCheckout_<checkoutSessionId>, Purchase_<orderId>), so Meta and TikTok count each one once. They fire on the pixels bound to InitiateCheckout and Purchase on the checkout step.
If your checkout step continues into a hosted TagadaPay page (next: ['@hosted']), the SDK does not fire Purchase: the hosted thank-you page fires it, and Snapchat, Google Tag Manager and Clarity have no event id to deduplicate a second one.
Identify and consent
identifyhands the visitor’s email, phone and TagadaPay customer id to the pixels for their advanced matching.- Pixels fire without waiting for consent.
consent({ marketing: false })stops them for the rest of the page and drops the events still waiting; a laterconsent({ marketing: true })on the same page does not turn them back on. Call it as soon as your consent banner knows the answer.
What changes when you upgrade
Nothing, until you calltrack.step(). The SDK makes no tracking request and loads no pixel before that first call, so a site that upgrades without touching its code behaves exactly as before.
- The first
track.step()turns tracking on for this browser and this store (a__tgd_trackentry inlocalStorage). From then on, the checkout and purchase pixels above fire on later page loads too. - Every tracking method returns at once on the server, so server-side rendering is safe.
- The pixel code is not in the main bundle: it is loaded on first use. With the CDN script tag, it is a second file,
tagada-pixels.min.js, fetched from jsDelivr. - Tracking never throws. A blocked pixel or a failed request is recorded and reported in SDK health, never surfaced to the visitor.
- If your site sends a Content-Security-Policy, allow the pixel origins listed in Content-Security-Policy.
Check that it works
In the browser,await tagada.track.health() tells you what this page did: whether tracking is on, the funnel key, the pixels loaded, and what was blocked.
npx tagada funnel check tells you what TagadaPay has seen from every visitor: steps never reached, events never received, pixels blocked, an outdated SDK, a wallet domain not registered. Each check is explained on SDK health.
The endpoints behind it
The SDKs call these routes; you do not need to call them yourself. They are listed so you know what leaves your site. They are not in the API Reference tab.
The two browser routes take no API key and no custom header, so they work from any domain without a CORS preflight. They only accept data for hosts listed in
domains (plus localhost), never carry a payment, and answer the same way for a store that does not exist. A request to them that carries an Authorization header is reported in SDK health as a secret key used from a browser.