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.
Using the npm package
Section titled “Using the npm package”If your frontend is built with a bundler (rollup, webpack, esbuild, vite, etc.), you can install the loader directly in your project.
npm install @whitebeardnl/messaging-loaderimport { init } from '@whitebeardnl/messaging-loader';
init({ siteId: 1, apiBase: 'https://messaging.domain.com/', assetBase: 'https://api.domain.com/',});Using a Tag Manager
Section titled “Using a Tag Manager”Add the following script tag to every page where messaging should appear, typically in the <head> or just before </body> or in your Tag Manager:
<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>Dev mode
Section titled “Dev mode”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.
Advanced Configuration
Section titled “Advanced Configuration”| Attribute | Config field | Required | Default | Description |
|---|---|---|---|---|
data-site-id |
siteId |
Yes | — | Publication ID for the site, as configured in the CMS. Must resolve to a positive number — missing/invalid values fire a wb:loader:error (reason: "missing-site-id") and abort the load. |
data-api-base |
apiBase |
Yes | - | Base URL of the Messaging API. |
data-asset-base |
assetBase |
Yes | - | Base URL for CMS API (used to resolve message CSS/JS assets). |
data-endpoint |
endpoint |
No | /messaging/embed |
Path (relative to apiBase) the loader posts the embed request to. |
data-zone-selector |
zoneSelector |
No | [data-wb-zone-key] |
CSS selector used to specify the attribute to look for when finding zones. |
data-inline-selector |
inlineSelector |
No | (auto-detected, see Inline Article Targeting) | CSS selector used to inject inline article messages into the page content. |
data-context |
context |
No | {} |
Extra targeting context forwarded to the API. As a data-* attribute this must be a JSON string (e.g. data-context='{"tier":"premium"}'). |
Standard templates (banners, modals, stacked panels, etc.) require no further frontend work — the loader handles rendering and basic interactions automatically.
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.
Custom zone attribute
Section titled “Custom zone attribute”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>Inline Article Targeting
Section titled “Inline Article Targeting”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>Blocking the remainder of the article
Section titled “Blocking the remainder of the article”When a message’s placement has “Block remainder of article” enabled, the loader hides the rest of the article once the message mounts, and (if the message is dismissible) restores it when the visitor dismisses the message.
By default the loader determines what to hide by walking the DOM after the mounted message and removing everything that follows it inside the article body.
If your page already renders its own paywall teaser, you can take full control instead by wrapping that teaser (plus whatever CTA you already show) in an element with the article-paywall class, as a sibling of your article body element:
<div class="article-paywall">…your teaser content…</div><div class="article-content" data-wb-article-body>…full article…</div>When .article-paywall is present, the loader appends the message inside it, hides the article body element entirely, and — on dismiss — removes the .article-paywall element and un-hides the article body, restoring it in full.
Advanced Templates
Section titled “Advanced Templates”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.
Template: Registration Wall
Section titled “Template: Registration Wall”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.
Events
Section titled “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.
How it works
Section titled “How it works”When the visitor clicks the Register Now button, the messaging tool:
- Disables the button to prevent duplicate submissions.
- Fires
wb:message:conversiononwindowwith the collectedfieldsand aresolve/rejectpair. - Waits for the frontend to call
resolve()orreject(error).- On
resolve()→ fireswb:message:conversion:successand leaves the button disabled. - On
reject(error)→ displays the error message inside the wall, re-enables the button, and fireswb:message:conversion:error.
- On
Frontend implementation
Section titled “Frontend implementation”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.')));Reloading on success
Section titled “Reloading on success”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.