Fire a journey from your site, read its reply, and let the reply render banners, popups and content swaps on the page.
A journey that starts with a Webhook trigger and ends in a Webhook Response node is a request and a reply. Your site calls it, the journey decides, and the reply comes back in the same request. The trigger id to call is shown on the Webhook trigger node in the builder.
A trigger only answers when its journey is published and live. A draft journey accepts nothing.
import { useWebhook } from "@xenia-martech/sdk-js/client/react";
function OfferButton() {
const { send, response, isLoading, error } = useWebhook("<trigger id>");
return <button onClick={() => send({ page: "pdp" })}>Show my offer</button>;
}The Webhook Response node returns whatever JSON you design. The SDK keeps the last
reply per trigger, readable with getResponse(triggerId) or the
useXeniaResponse() hook.
To change the page, the reply includes an xeniaActions array. The browser SDK
applies them automatically.
{
"xeniaActions": [
{ "type": "swap", "target": "#hero", "html": "<b>Welcome back!</b>" },
{ "type": "hide", "target": ".guest-banner" },
{
"type": "banner",
"message": "20% off today",
"cta": { "label": "Shop", "url": "/sale" },
"dismissAfterMs": 8000,
"position": "bottom"
}
]
}target is a CSS selector, or the id in a data-xenia-slot="…" attribute.cta is { label, url?, event? }. Clicking a CTA with an event tracks it.position is a corner for popup (top-left, top-right, bottom-left,
bottom-right, default bottom-right) or an edge for banner (top,
bottom, default bottom). Modals are always centered.Messages render in an isolated shadow root, so your site's styles never leak into them, and theirs never leak out.
The built-in message card can be restyled, replaced per message, or replaced everywhere.
Every visual value on the built-in card is a CSS custom property. Set them in your own stylesheet and they reach inside the shadow root:
:root {
--xenia-card-bg: #111827;
--xenia-card-color: #f9fafb;
--xenia-cta-bg: #6366f1;
--xenia-card-radius: 16px;
}Set html on a single action to use that markup instead of the card. To replace
the card everywhere, register a template once:
createXeniaClient({
// ...
messageTemplate: ({ action, variant, escapeHtml }) =>
`<div class="my-${variant}">${escapeHtml(action.message ?? "")}</div>`,
messageStyles: `.my-banner { background: #111; color: #fff; }`,
});Inside any custom markup, data-xenia-close dismisses the message and
data-xenia-cta fires the action's CTA.
messageComponent mounts a real component instead of an HTML string. React has a
ready-made adapter:
import {
XeniaProvider,
reactMessageComponent,
type XeniaMessageComponentProps,
} from "@xenia-martech/sdk-js/client/react";
function OfferCard({ action, close, fireCta }: XeniaMessageComponentProps) {
return (
<div className="offer-card">
<p>{action.message}</p>
{action.cta && <button onClick={fireCta}>{action.cta.label}</button>}
<button onClick={close} aria-label="Close">×</button>
</div>
);
}
<XeniaProvider
tenantId="<workspace id>"
environmentKey="<environment>"
messageComponent={reactMessageComponent(OfferCard)}
>
<App />
</XeniaProvider>;For other frameworks, pass messageComponent: (container, ctx) => { … } that mounts
into container and returns an unmount function. Precedence is: an action's own
html, then messageComponent, then messageTemplate, then the built-in card.
An action with widgetType: "spin-wheel" renders a prize wheel. The prize weights
and codes stay on the server, and the outcome is always decided there. The browser
never guesses a result.
The spinner, win and loss screens are your own HTML, so your markup is the widget. The SDK only needs a few attributes inside them:
A visitor who already played sees their earlier outcome again rather than a new spin.
Any widget can capture input. Mark inputs with data-xenia-field="<name>" and the
submit control with data-xenia-submit. The SDK shows a spinner while the request
is in flight, reveals an optional <div data-xenia-success hidden> block on
success, and shows a retry message in data-xenia-error on failure.