<zalient-agent>

The Zalient seller assistant, as one tag a shop dashboard drops in. One script, one custom element, a shadow root the host page cannot reach into and that cannot reach back out.

See it running

The whole integration

<script src="https://durbinapp.com/agent.v1.js" async></script>
<zalient-agent
  session-token="eyJhbGciOi..."
  refresh-url="/api/agent-token"
></zalient-agent>

Everything else lives in the bundle: layout, state, the question blocks, the confirmation cards. Nothing about the widget’s internals is public API, so none of it can break your page and your page cannot break it.

Identity is never an attribute

There is no shop-id, and there will not be one. Your backend mints a short-lived signed token; the widget posts it to ours; ours verifies the signature, issuer, audience, expiry and origin, and answers with the shop. Every shop identity downstream comes from that answer. A seller editing the attribute in devtools gets a failed verification, not another shop’s catalogue.

The session token lives in a private field. Not localStorage, which every script on your page can read.

Three attributes

session-tokenrequiredA short-lived JWT your backend signed. Not an id, not a slug.
refresh-urloptionalA route on your own origin that mints a fresh one. Without it the widget stops when the first token expires.
themeoptionallight · dark · auto. Absent behaves as auto.

Three events

zalient:ready{ shop }The session was established.
zalient:action{ action, kind, summary }A write landed. Your cue to refetch.
zalient:error{ message, fatal }For your telemetry. The widget shows its own errors.
document.addEventListener("zalient:action", (event) => {
  // { action, kind, summary } — a write landed. Refetch your table.
  ordersTable.refetch();
});

All three set composed: true, so they cross the shadow boundary. Outside it, event.target is <zalient-agent> rather than whatever was clicked inside.

Theming

Six custom properties are public, and each is a promise kept across versions: --zl-accent, --zl-surface, --zl-ink, --zl-radius, --zl-font, --zl-height. No ::part(): parts are a commitment to an internal structure, and the internal structure is what we most want to stay free to change.

What you need to allowlist

script-src  https://durbinapp.com
connect-src https://agent.zalient.shop

That is the whole list. No eval, no inline styles injected into your document, no external font or image. The widget touches no global except the element name and one frozen object with two strings for support triage.