Skip to content

Messaging Tool Frontend Implementation

The messaging tool allows managing all on-site messaging from the backend using simple frontend integrations. Banners, modals, inline article prompts, and registration walls can all be configured and targeted from the CMS — with no frontend deployments required to update your messages.

Add the following script tag to every page where messaging should appear, typically in the <head> or just before </body>:

<script
data-api-base="https://messaging.domain.com/"
data-asset-base="https://api.domain.com/"
data-site-id="1"
type="text/javascript"
src="https://api.domain.com/js/messaging_loader.js"
async="true"
></script>
Attribute Description
data-inline-selector CSS selector used to inject inline article messages into the page content
data-zone-selector CSS selector used to specify the attribute to look for when finding zones
data-api-base Base URL of the Messaging API
data-asset-base Base URL for CMS API
data-site-id Publication ID for the site, as configured in the CMS

Standard templates (banners, modals, stacked panels, etc.) require no further frontend work — the loader handles rendering and basic interactions automatically.

For developers testing the worker, you can use the following local environment URLs:

<script
data-api-base="http://localhost:8787/"
data-asset-base="http://localhost:8086/"
data-site-id="1"
type="text/javascript"
src="http://localhost:8086/js/messaging_loader.js"
async="true"
></script>

Make sure to also run Vite in messaging mode and to run the messaging worker.


Messages can be targeted to specific locations on the page using zone keys, similar to how a DFP ad unit works. Place a zone container anywhere in your HTML:

<div data-wb-zone-key="zone_name"></div>

Then use that zone name when configuring the message placement in the CMS. The loader automatically discovers all zone containers on the page and includes their keys in the API request.

By default the loader looks for [data-wb-zone-key]. You can change this by setting data-zone-selector on the script tag:

<script
data-site-id="1"
data-zone-selector="[data-my-zone]"
src="https://api.domain.com/js/messaging_loader.js"
async="true"
></script>

Messages placed inside article content use a CSS selector to find the article body. You can specify one explicitly with data-inline-selector on the script tag. If omitted, the loader tries each of these selectors in order and uses the first match:

Priority Selector
1 [data-wb-article-body]
2 article .article-body
3 article .content-body
4 article
6 .article-texts

The recommended approach is to add data-wb-article-body to your article body element so the selector is explicit and immune to markup changes:

<div class="article-content" data-wb-article-body></div>

Some templates expose interactive behaviour that depends on site-specific logic such as captcha services, authentication flows, or subscription APIs. These require a frontend event integration.


The registration wall collects a visitor’s form input and delegates the full conversion flow — including captcha verification and the API call — to the frontend via a custom DOM event. The event contract is generic (wb:message:conversion*, not wb:message:register*), since the same wall markup can back other conversion actions besides registration (e.g. login, newsletter signup) — any template that opts into this flow fires the same events.

Event Fired on Detail
wb:message:conversion window { messageId, templateKey, zoneKey, surface, variantId, fields, resolve, reject }
wb:message:conversion:success window { messageId, templateKey, zoneKey, surface, variantId, fields }
wb:message:conversion:error window { messageId, templateKey, zoneKey, surface, variantId, fields, error }

fields is an object built from every named <input>/<select>/<textarea> inside the message, keyed by each element’s name attribute exactly as it appears in the HTML — for the registration wall that’s { full_name, email, password }. Because the messaging tool doesn’t know in advance which fields a given template’s form has, it forwards all of them verbatim rather than picking out specific ones, so a future template with different fields (a login form with just login_email/login_password, for example) works without any JS changes here.

Because the same event fires for every template that opts into this flow, use templateKey inside your handler to decide which real-world action to perform (register, log in, subscribe to a newsletter, etc.) — see the code sample below. Today registration_wall is the only template wired to this event, but the templateKey check future-proofs your integration against additional conversion-style templates without needing a new event name for each one.

When the visitor clicks the Register Now button, the messaging tool:

  1. Disables the button to prevent duplicate submissions.
  2. Fires wb:message:conversion on window with the collected fields and a resolve/reject pair.
  3. Waits for the frontend to call resolve() or reject(error).
    • On resolve() → fires wb:message:conversion:success and leaves the button disabled.
    • On reject(error) → displays the error message inside the wall, re-enables the button, and fires wb:message:conversion:error.

Listen for wb:message:conversion, branch on templateKey to identify which conversion action this is, and perform your captcha challenge and API call inside the handler:

window.addEventListener("wb:message:conversion", (event) => {
const { templateKey, fields, resolve, reject } = event.detail;
if (templateKey !== "registration_wall") {
// Handle other conversion-form templates here as they're introduced
// (e.g. a future "login_wall" or "newsletter_wall" templateKey).
return;
}
const { full_name: name, email: email, password: password } = fields;
yourCaptcha
.execute()
.then((token) => yourApi.register(name, email, password, token))
.then(resolve)
.catch(reject);
});

If registration fails, throw or reject with an Error whose message property contains a human-readable string — it will be shown directly to the user inside the wall:

.catch(err => reject(new Error('This email is already registered.')));

On a successful registration the visitor typically gains access to content, so you will usually want to reload the page:

window.addEventListener("wb:message:conversion:success", () => {
window.location.reload();
});

If you need to do something before reloading (e.g. set a session cookie, fire an analytics event), do it before calling resolve() inside the wb:message:conversion handler rather than in the success listener, so the reload only happens once everything is confirmed.