How to Add a VBWD Tariff-Plan CTA to Any External Page

Drop a live ‘choose a plan’ call-to-action onto any external site: expose plans via the public API, use the embeddable pricing widget, deep-link to VBWD checkout, add UTM tracking and style it to match.

A practical, step-by-step guide.

You run a landing page, a WordPress blog, or a Webflow marketing site, and your subscription plans live inside a self-hosted VBWD instance. You want visitors to see your current tariff plans and click straight through to checkout, without rebuilding the pricing table by hand every time a price changes. This guide shows two ways to do that: paste an embeddable pricing widget, or read the public plans API and render your own cards. Both keep the plans in one place, the admin, so the external page never drifts out of sync.

VBWD is self-hosted and source-available (Python/Flask, PostgreSQL, Vue 3, Docker), licensed under BSL 1.1 with an Additional Use Grant that keeps it free for commercial use while annual VBWD-attributable sales stay under the value of 6.7 BTC. If you have not stood up an instance yet, the documentation covers install; this article assumes you already have one running.

What you will end up with

A block on an external page, on a domain you do not control from inside VBWD, that lists live plan names, prices, and currencies, and whose buttons deep-link into the VBWD checkout. When a plan price changes in the admin, the external block updates on next load with no code deploy.

Prerequisites

  • A running VBWD instance with a reachable base URL, for example https://app.example.com.
  • Admin access to configure plans and CORS. See how plans map to tariffs if you are still deciding on your tiers.
  • Edit access to the external page (HTML, a WordPress custom-HTML block, or a Webflow Embed element).
  • Your instance running over HTTPS. Browsers block mixed content, so an https page loading an http API or script will silently fail.

Step 1: Configure your tariff plans in the admin

Everything downstream reads from the plans you define in the VBWD admin backoffice. Open Plans, and for each tier set at least a name, a price, a currency, and a slug. The slug is the stable identifier the checkout deep-link uses, so pick something durable like starter, pro, or team and avoid renaming it later.

Keep this admin the single source of truth. Do not hardcode prices on the external page. The whole point of the embed is that the marketing site reflects whatever the admin currently says. If you maintain plans in two places, they will disagree within a week.

Step 2: Allow your external origin (CORS)

This is the step people miss, and then they file a bug that isn’t one. The external page lives on a different origin than your VBWD instance. A browser will refuse the cross-origin request unless VBWD explicitly allows that origin. In your instance environment, add the marketing domain to the allowed-origins list:

# .env on the VBWD instance
VBWD_ALLOWED_ORIGINS=https://www.example-marketing.com,https://example-marketing.com

List every variant your visitors actually hit, with and without www, plus any staging domain you test from. After changing this, restart the API container so the new configuration is read:

docker compose up -d --force-recreate --no-deps api

If you skip this step, the embed or fetch throws a CORS error in the browser console and renders nothing. There is no way around it from the page side; the allowance has to come from the instance. The configuration reference lists the exact variable names for your version.

Step 3a: Paste the embeddable pricing widget

The fastest path is the embeddable pricing widget: a single script tag you paste onto any external site. It fetches your live plans, renders a pricing table, and wires each button to checkout. Drop this where you want the table to appear:

<div id="vbwd-pricing"></div>
<script
  src="https://app.example.com/embed/pricing-widget.js"
  data-target="#vbwd-pricing"
  data-currency="EUR"
  defer>
</script>

In WordPress, put both lines in a Custom HTML block. In Webflow, use an Embed element. On a static site, paste them into the page body. The widget reads plans from the same instance that served the script, so there is nothing else to point it at.

Step 3b: Or read the plans API and render your own cards

If you want full control over markup, skip the widget and call the public plans endpoint directly. GET /api/v1/tarif-plans/ returns your plans with name, price, currency, and slug. It is a public read endpoint, so no auth token is needed for listing.

<div id="plans"></div>
<script>
const BASE = "https://app.example.com";

fetch(`${BASE}/api/v1/tarif-plans/`)
  .then((response) => response.json())
  .then((data) => {
    const plans = data.items || data;
    const container = document.getElementById("plans");

    container.innerHTML = plans.map((plan) => `
      <article class="vbwd-plan">
        <h3>${plan.name}</h3>
        <p class="price">${plan.price} ${plan.currency}</p>
        <a class="vbwd-cta"
           href="${BASE}/checkout?plan=${plan.slug}&utm_source=marketing-site&utm_medium=pricing&utm_campaign=cta">
          Choose plan
        </a>
      </article>
    `).join("");
  })
  .catch((error) => {
    console.error("Could not load VBWD plans:", error);
  });
</script>

The exact response shape may nest plans under a key such as items depending on your version, which is why the snippet guards with data.items || data. Check one real response in your browser first and adjust the mapping to match. Confirm field names against your instance rather than assuming; the four you need are name, price, currency, and slug.

Step 4: Deep-link the button into checkout

Whether you use the widget or your own cards, the button should carry the visitor into the VBWD checkout with the plan preselected. The deep-link is your instance URL plus the plan slug, for example https://app.example.com/checkout?plan=pro. Checkout, account creation, and payment all happen on the VBWD domain, not on your marketing site. That is deliberate: the external page is a shop window, and the transaction runs where the billing logic and payment providers live. For how checkout and invoicing behave after the click, see the billing overview.

Step 5: Add UTM tracking

You will want to know which external page drove which subscription. Append UTM parameters to the checkout link, as shown in the fetch snippet above:

?plan=pro&utm_source=marketing-site&utm_medium=pricing&utm_campaign=spring-launch

Use a distinct utm_source per external property, so a Webflow landing page and a WordPress blog post report separately. If you run the embeddable widget, check whether it forwards query parameters you set on the container or whether you need the custom-cards approach to attach UTMs; the API route gives you that control outright.

Step 6: Style with CSS variables

Both the widget and your own cards can be themed without touching internal markup. Define CSS custom properties on the container so the block matches your brand:

#vbwd-pricing, #plans {
  --vbwd-cta-bg: #1a7f5a;
  --vbwd-cta-text: #ffffff;
  --vbwd-plan-radius: 12px;
}

.vbwd-plan {
  border-radius: var(--vbwd-plan-radius);
  padding: 1.5rem;
}

.vbwd-cta {
  background: var(--vbwd-cta-bg);
  color: var(--vbwd-cta-text);
  padding: 0.6rem 1.2rem;
  border-radius: 8px;
  text-decoration: none;
  display: inline-block;
}

Keep colour in variables with fallbacks so a missing token degrades to a sane default instead of an unstyled button.

Verify it works

  1. Load the external page and open the browser console. A CORS error means Step 2 is incomplete; go back and add the origin, then recreate the API container.
  2. Confirm the plan names and prices on the page match what the admin shows. If they differ, you are probably rendering hardcoded values, not the API.
  3. Change a price in the admin, reload the external page, and confirm the new price appears. This proves the block is live, not a snapshot.
  4. Click a button and confirm it lands on the VBWD checkout with the right plan preselected.
  5. Complete one test subscription and check that the UTM source shows up in your analytics or invoice metadata.

Limits and honest caveats

A few things worth stating plainly:

  • The embed will not load cross-site without correct allowed-origin config. This is the most common failure. It is a browser security boundary, not a VBWD bug, and it can only be fixed on the instance.
  • Checkout stays on the VBWD domain. You cannot fully inline the payment flow into your marketing site. The button is a deep-link; the transaction runs where the billing engine and payment adapters live.
  • Keep plans in one place. The admin is the source of truth. The embed’s only advantage over a hand-built table is that it stays in sync, and that advantage evaporates the moment you duplicate prices into the page.
  • Response shapes vary by version. Do not copy field names blind; check one real API response first.
  • HTTPS end to end. Mixed-content blocking will kill an http API call from an https page with no visible error beyond the console.

If you want to go further, some deployments expose pricing and related surfaces as plugins you can enable per instance; the plugin catalogue is the place to check what your build ships.

Takeaway

Two moving parts get you a live pricing CTA on any external page: define plans once in the admin, and either paste the embeddable widget or fetch GET /api/v1/tarif-plans/ and render your own cards. Allow the external origin, deep-link the button into checkout, tag it with UTMs, and theme it with CSS variables. The reason to do it this way rather than hand-coding a table is sync: the marketing page always shows what the admin currently sells, and every click lands in the right place on the VBWD domain where billing actually runs.

Learn more about VBWD

VBWD is a self-hosted, source-available platform for building subscription products, marketplaces, and AI-powered apps. Explore it further:

VBWD is source-available — get the SDK on GitHub.

Happy
Happy
0%
Sad
Sad
0%
Excited
Excited
0%
Angry
Angry
0%
Surprise
Surprise
0%
Sleepy
Sleepy
0%

How to Configure a Sales Bot With VBWD

How to Turn a GitHub Repo Into a Paid Product With GHRM

Leave a Reply

Your email address will not be published. Required fields are marked *

4 × 4 =