Skip to main content

Host & A/B Test Any Page

This is a low-level developer tutorial. It walks through raw REST API calls to deploy files, mount routes, and configure A/B tests manually.Most merchants don’t need this — the TagadaPay dashboard and CLI handle it for you. This guide is for developers who are:
  • Building an external page builder or CI/CD pipeline that pushes to TagadaPay
  • Hosting a standalone website or SPA on TagadaPay’s edge CDN
  • Integrating TagadaPay’s hosting and A/B infrastructure into their own tooling
If you’re building a checkout plugin, start with the Plugin SDK Quick Start instead.
TagadaPay can host any static HTML page or single-page application and serve it on a fast global CDN (~10ms TTFB). You can then split traffic between variants using weighted or geo-based A/B testing — all through the REST API.

Why Host on TagadaPay Instead of Your Own Servers?

BenefitDetails
Edge-fast servingPages are served from a global edge network (~10 ms TTFB). A/B variant selection happens at the edge too — no extra round-trip to an origin server, so visitors never feel the split.
Same-URL A/B testingBoth variants live on the exact same URL. The routing engine picks the right one transparently. This is critical for ad campaigns, email links, and SEO — you don’t need separate URLs or client-side redirect hacks.
Custom domains & routing in one clickBring your own domain, point a CNAME, verify via API, done. TagadaPay handles TLS, routing, and CDN caching automatically. No nginx configs, no Cloudflare rules, no load balancers.
Free hosting for TagadaPay usersHosting is included at no extra cost when you use TagadaPay. No bandwidth bills, no storage fees, no infra to maintain.

Use Cases

Use caseExample
Landing page testingTest two headline variants to see which converts better
Geo-targeted contentShow a French page to EU visitors, English to US visitors
Gradual rolloutsSend 10% of traffic to a new design before going 100%
External SPAsHost a React/Vue/Svelte app without managing your own infra
Every page you deploy gets:
  • A unique URL on *.cdn.tagadapay.com (or your own domain)
  • Instant global delivery via edge CDN
  • Built-in A/B split testing (weighted or geo) on the same URL
  • Sticky sessions so returning visitors see the same variant

Prerequisites

Sign up at app.tagadapay.com and create a store.
Go to Settings → API Keys in your dashboard and generate one. It looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
All examples use curl. You can use Postman, httpie, or any language’s HTTP library.

The 3-Step Flow

Every deployment follows the same pattern:
Deploy → Instantiate → Mount
StepWhat it does
DeployUploads your files (HTML, CSS, JS, images) to blob storage
InstantiateCreates a running instance of that deployment for a store
MountBinds the instance to a URL so visitors can access it
After that, you can optionally configure A/B split testing on the mounted route.

Step 1 — Deploy a Page

Create a simple HTML file and deploy it via the API. Assets are sent as base64-encoded strings.
1

Prepare your HTML

Any valid HTML works. Here’s a minimal example:
<!DOCTYPE html>
<html>
<head><title>My Landing Page</title></head>
<body>
  <h1>Welcome!</h1>
  <p>This page is hosted on TagadaPay.</p>
</body>
</html>
2

Base64-encode the file

base64 -i index.html
# Copy the output — you'll paste it in the next step
3

Call the Deploy API

curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/deploy \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "YOUR_STORE_ID",
    "manifest": {
      "name": "my-landing-page",
      "version": "1.0.0",
      "pages": [
        { "id": "main", "path": "/", "name": "Landing Page" }
      ]
    },
    "inlineAssets": [
      {
        "path": "/index.html",
        "content": "PASTE_BASE64_HERE",
        "contentType": "text/html"
      }
    ]
  }'
Save the pluginId and deploymentId from the response — you’ll need them next.
You can deploy multiple files (CSS, JS, images) by adding more entries to inlineAssets. Each file needs a path, base64 content, and contentType.

Step 2 — Instantiate

An instance is a “live copy” of a deployment, bound to a specific store. You can create multiple instances of the same deployment with different configurations.
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/instantiate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pluginId": "PLUGIN_ID_FROM_STEP_1",
    "deploymentId": "DEPLOYMENT_ID_FROM_STEP_1",
    "storeId": "YOUR_STORE_ID",
    "config": {}
  }'
Save the instanceId from the response.

Instance Config

The config object is optional but powerful. Whatever you pass here is injected into the served HTML as window.__TAGADA_PLUGIN_CONFIG__, making it available to your JavaScript at runtime. This lets you deploy one set of files and customize behavior per-instance without redeploying.
# Same deployment, different config per store/use case
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/instantiate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pluginId": "PLUGIN_ID",
    "deploymentId": "DEPLOYMENT_ID",
    "storeId": "YOUR_STORE_ID",
    "config": {
      "theme": "dark",
      "headline": "Spring Sale — 30% Off!",
      "ctaUrl": "https://yourstore.com/checkout",
      "showTimer": true,
      "timerEnd": "2026-04-01T00:00:00Z"
    }
  }'
Then read it in your page:
<script>
  var cfg = window.__TAGADA_PLUGIN_CONFIG__ || {};
  document.querySelector('h1').textContent = cfg.headline || 'Welcome';
  if (cfg.showTimer) startCountdown(cfg.timerEnd);
</script>
You don’t have to read window.__TAGADA_PLUGIN_CONFIG__ manually. The TagadaPay Plugin SDK reads it for you and exposes it via tgd.config. Using the SDK is the recommended approach — it also gives you session management, analytics, and funnel navigation out of the box. Reading the global directly is perfectly valid if you want zero dependencies, but for most use cases the SDK is simpler.
Additional globals like window.__TAGADA_STORE_ID__ and window.__TAGADA_ACCOUNT_ID__ (and matching x-plugin-* meta tags) are always injected regardless of the config you pass.
Common use cases:
Config fieldPurpose
theme, colorsBrand customization without redeploying assets
headline, ctaCopy testing across multiple instances
locale, currencyLocalization per region (combine with geo A/B)
featureFlagsGradual feature rollout
analyticsIdPer-store tracking IDs

Step 3 — Mount

Mounting binds your instance to a hostname so it becomes reachable by visitors.
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/mount \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instanceId": "INSTANCE_ID_FROM_STEP_2",
    "storeId": "YOUR_STORE_ID",
    "hostname": "my-landing-page--YOUR_STORE_ID.cdn.tagadapay.com",
    "basePath": "/",
    "matcher": "/"
  }'
Save the routeId from the response — you’ll need it if you set up A/B testing. Your page is now live at:
https://my-landing-page--YOUR_STORE_ID.cdn.tagadapay.com/

Set Up A/B Testing

You can split traffic on any mounted route. TagadaPay supports two split types:
TypeHow it works
weightedSplit by percentage (e.g. 50/50, 90/10)
geoSplit by visitor country (ISO country codes)

Create a Second Variant

A variant is just another instance. There are two ways to create one:
Deploy entirely different HTML/CSS/JS (Step 1), then instantiate it (Step 2). Each variant has its own build assets — useful when the pages are structurally different (e.g. a completely redesigned layout vs. the original).You’ll get a separate pluginId, deploymentId, and instanceId for the variant.
You do not mount the second variant separately. Both variants share the same URL — the split testing system decides which one to serve.

Configure the Split

Call the split endpoint with the routeId from Step 3 and both instance IDs:
Route visitors to different variants based on their country.
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/split \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "routeId": "ROUTE_ID_FROM_STEP_3",
    "storeId": "YOUR_STORE_ID",
    "splitConfig": {
      "type": "geo",
      "instances": [
        {
          "instanceId": "INSTANCE_A",
          "deploymentId": "DEPLOYMENT_A",
          "pluginId": "PLUGIN_A",
          "regions": ["US", "CA", "MX", "BR"]
        },
        {
          "instanceId": "INSTANCE_B",
          "deploymentId": "DEPLOYMENT_B",
          "pluginId": "PLUGIN_B",
          "regions": ["FR", "DE", "GB", "ES", "IT"]
        }
      ],
      "stickyDuration": 604800
    }
  }'
Visitors from unlisted countries fall back to the first instance.

How Sticky Sessions Work

When a visitor hits a split route for the first time, TagadaPay picks a variant and stores the choice in a sticky session cookie (tgd-session-id). On subsequent visits within stickyDuration seconds (default: 7 days), the same variant is served. This ensures a consistent experience — a visitor won’t flip between variants mid-session.

Update a Variant

To push a new version of a variant, deploy with "overwrite": true:
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/deploy \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "YOUR_STORE_ID",
    "manifest": {
      "name": "my-landing-page",
      "version": "1.1.0",
      "pages": [
        { "id": "main", "path": "/", "name": "Landing Page" }
      ]
    },
    "inlineAssets": [
      {
        "path": "/index.html",
        "content": "NEW_BASE64_CONTENT",
        "contentType": "text/html"
      }
    ],
    "overwrite": true
  }'
The update propagates automatically to all routes using this deployment. No re-mount needed.

Deploying a Full SPA

TagadaPay can host any single-page application (React, Vue, Svelte, etc.). Build your app, then deploy all output files:
# Build your app
npm run build

# Encode each file and include in inlineAssets
# Example for a Vite React app (dist/ folder):
{
  "storeId": "YOUR_STORE_ID",
  "manifest": {
    "name": "my-react-app",
    "version": "1.0.0",
    "pages": [
      { "id": "main", "path": "/", "name": "App" }
    ]
  },
  "inlineAssets": [
    { "path": "/index.html", "content": "...", "contentType": "text/html" },
    { "path": "/assets/index-abc123.js", "content": "...", "contentType": "application/javascript" },
    { "path": "/assets/index-def456.css", "content": "...", "contentType": "text/css" }
  ]
}
All non-HTML paths (JS, CSS, images, fonts) are served with long-lived cache headers. HTML is served with no-cache for instant updates.

Advanced: Large File Uploads (> 4 MB)

The inlineAssets method shown above sends base64-encoded files inside the JSON body. This works well for small pages, but serverless functions have a ~4.5 MB body limit. If your SPA build output is larger — bundled JS, images, fonts — you need the two-step blob upload flow instead.
Instead of embedding assets in the request body, you:
  1. Upload a ZIP of your build output to TagadaPay’s blob storage, which supports multipart uploads up to 50 MB.
  2. Deploy from the blob URL — pass the returned URL to the deploy endpoint, which downloads and extracts the ZIP server-side.
This is the same flow the TagadaPay CLI and Studio use under the hood. The CLI automatically switches to blob upload when a build exceeds 4 MB.
1

ZIP your build output

cd dist/          # or build/, out/, etc.
zip -r ../my-app.zip .
cd ..
2

Get an upload token

The upload token endpoint implements a client upload protocol. You need to call it twice: once to get a token, then once more to confirm completion. In practice, use the @vercel/blob client library — it handles both calls for you.
npm install @vercel/blob
import { upload } from '@vercel/blob/client';
import { readFileSync } from 'fs';

const zipBuffer = readFileSync('my-app.zip');

const blob = await upload('my-app.zip', zipBuffer, {
  access: 'public',
  handleUploadUrl: 'https://app.tagadapay.com/api/public/v1/plugins/generate-upload-token',
  multipart: true,
  contentType: 'application/zip',
  clientPayload: JSON.stringify({ apiKey: 'YOUR_API_KEY' }),
});

console.log('Uploaded to:', blob.url);
3

Deploy from the blob URL

Now pass the blob URL to the V2 deploy endpoint using the zipUrl parameter instead of inlineAssets:
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/deploy \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "YOUR_STORE_ID",
    "manifest": {
      "name": "my-react-app",
      "version": "1.0.0",
      "pages": [
        { "id": "main", "path": "/", "name": "App" }
      ]
    },
    "zipUrl": "BLOB_URL_FROM_PREVIOUS_STEP"
  }'
The server downloads the ZIP, extracts all files, and uploads each one to blob storage — exactly like inlineAssets, but without the body size limit.
4

Continue as usual

From here, the flow is identical: instantiatemount → optionally configure A/B testing. The pluginId and deploymentId from the response are the same as with inline assets.
MethodMax sizeBest for
inlineAssets (base64 in JSON)~4 MB total payloadSingle HTML pages, small sites, quick tests
zipUrl (blob upload)50 MBFull SPAs, sites with images/fonts, production builds
If you use the TagadaPay CLI, you don’t need to think about this — it detects the file size and picks the right method automatically.

Custom Domain

By default your page is served on *.cdn.tagadapay.com. You can mount it on your own domain instead using the Domains API.
1

Register the domain

Call the domains API to add your domain to TagadaPay. This registers it with the edge network and returns a verification token.
curl -X POST https://app.tagadapay.com/api/public/v1/domains/add \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "landing.yourstore.com"
  }'
Response:
{
  "success": true,
  "domain": {
    "id": "cdm_abc123",
    "domain": "landing.yourstore.com",
    "isVerified": false,
    "enabled": false,
    "verificationToken": "vrf_token_xyz"
  }
}
Save the id — you’ll use it to verify.
2

Configure DNS

Add the required DNS records at your registrar. You need a CNAME pointing to cname.vercel-dns.com:
TypeNameValue
CNAMElandingcname.vercel-dns.com
Wait for DNS propagation (usually a few minutes, can take up to 48h).
3

Verify the domain

Once DNS has propagated, call the verify endpoint:
curl -X POST https://app.tagadapay.com/api/public/v1/domains/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domainId": "cdm_abc123"
  }'
A successful response looks like:
{
  "status": "Valid Configuration",
  "ownershipStatus": "Verified"
}
4

Mount on the custom domain

Once verified, mount your instance using the customDomain field:
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/mount \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "deploymentId": "YOUR_DEPLOYMENT_ID",
    "deploymentInstanceId": "YOUR_INSTANCE_ID",
    "storeId": "YOUR_STORE_ID",
    "customDomain": "landing.yourstore.com",
    "basePath": "/",
    "matcher": "/"
  }'
Your page is now live at https://landing.yourstore.com/.
HTTPS is provisioned automatically. A/B testing, sticky sessions, and all other features work identically on custom domains.
You can also list and remove domains:
# List all your domains
curl -X POST https://app.tagadapay.com/api/public/v1/domains/list \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

# Remove a domain
curl -X POST https://app.tagadapay.com/api/public/v1/domains/remove \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domainId": "cdm_abc123" }'

API Reference

EndpointMethodDescription
/api/public/v1/plugins/v2/deployPOSTUpload files and create a deployment (inlineAssets or zipUrl)
/api/public/v1/plugins/generate-upload-tokenPOSTGet a blob upload token for large files (multipart, up to 50 MB)
/api/public/v1/plugins/v2/instantiatePOSTCreate a live instance from a deployment
/api/public/v1/plugins/v2/mountPOSTBind an instance to a URL (hostname or custom domain)
/api/public/v1/plugins/v2/splitPOSTConfigure A/B testing on a route
/api/public/v1/domains/addPOSTRegister a custom domain
/api/public/v1/domains/verifyPOSTVerify DNS configuration
/api/public/v1/domains/listPOSTList all custom domains
/api/public/v1/domains/removePOSTRemove a custom domain
All endpoints require Authorization: Bearer YOUR_API_KEY and Content-Type: application/json.

Troubleshooting

Make sure the hostname in the mount request matches the URL you’re visiting. Format: {slug}--{storeId}.cdn.tagadapay.comDouble-check that basePath is / and matcher is /.
Sticky sessions may be pinning you to one variant. Clear your cookies or use incognito mode to get a fresh session. For geo testing, the system reads the visitor’s country from the edge network — make sure you’re testing from the expected country or use a VPN.
Ensure each asset’s path in inlineAssets matches the path referenced in your HTML. For example, if your HTML has <script src="/assets/app.js">, the asset path must be /assets/app.js.
The manifest object requires at minimum: name, version, and pages (array with at least one entry). Each page needs id, path (starting with /), and name.

Next Steps