Skip to main content

Drop in an Orbit widget with one HTML tag

Orbit Elements is the declarative surface of the Web SDK: five Custom Elements — <orbit-chat>, <orbit-verify>, <orbit-video-room>, <orbit-click-to-call>, and <orbit-preference-center> — that mount the SDK’s prebuilt widgets from plain HTML attributes. Paste a tag, and the element boots the same engine its imperative OrbitChat.init({...})-style counterpart runs — the elements are a layer on top of those engines, not a second implementation. Use Orbit Elements when the page has no JavaScript build pipeline — a CMS page, a Webflow site, a hand-written HTML template, an email-marketing landing page — and writing a <script type="module"> bootstrap is one build step too many. If you already import @devotel-orbit/web through a bundler, the imperative classes in the Web SDK reference give the same widgets with more control. Two facts stay true across every element:
  • The browser never carries an Orbit API key. A widget-id is a public identifier; every token, and the <orbit-verify> base-url proxy, is minted or supplied by your backend with your secret key (dv_live_sk_…). The elements inherit each engine’s authentication model unchanged.
  • Attributes are read once, when the tag connects. Changing an attribute after the element has mounted has no effect — remove and re-append the tag to re-mount with new values.

1. Install and register the elements

There is no Devotel-hosted CDN for the elements bundle — copy it out of the npm package and serve it from your own static host.
Then copy node_modules/@devotel-orbit/web/dist/orbit-elements.iife.js wherever your site serves static assets, and load it on the page:
The IIFE bundle registers all five tags on load — no further JavaScript is required. If you already bundle the SDK, register the same five tags with one call instead (the call is idempotent, and a no-op outside a browser, so it is safe in SSR):

2. Element matrix

The five elements, their required attributes, and the native events they re-dispatch:

<orbit-chat>

Mounts the tenant-scoped native chat widget — the same launcher the dashboard’s Settings → Channels → Native chat → Install snippet resolves to. The widget owns a fixed-position launcher on document.body; the element itself paints nothing — it is a config carrier that mounts the widget when it connects and destroys it when it disconnects. This element re-dispatches no events — drive it with the imperative widget API (see section 5) when you need unread-badge or open/close hooks.

<orbit-verify>

Paints the prebuilt verification UI — silent one-tap (operator-verified number authentication) with an OTP form as fallback — into the element itself. base-url must point at your backend proxy that fronts Orbit’s authenticated /verify routes with your secret key; the element’s network calls go to that proxy, and the browser still holds no Orbit key.

<orbit-video-room>

Paints the multi-party video room — participant tile grid, self-view, and the control bar — into the element, which is itself the container. Give the element an explicit height (a wrapper div or CSS on the tag) or the tiles collapse to zero. Both token and server-url come from your backend: minted via POST /api/v1/video/rooms/:id/join for authenticated participants, or via the anonymous POST /widget/video-session endpoint for public pages (the join response carries the token and the media URL). The full token-minting walkthrough is in the video-call button guide.

<orbit-click-to-call>

Mounts the click-to-dial iframe bridge — the surface a CRM sidebar embeds for click-to-call from a record page. token is minted server-side via POST /api/v1/voice/cti-embed/session. When to is set, the element dials that number as soon as the bridge iframe reports ready; omit to to show a bare surface and drive dial() from your own button (see section 5). Outbound dialing rides the same voice path your tenant’s calls already terminate on — the element adds no new carrier.

<orbit-preference-center>

Frames the hosted, per-contact preference page — the token-signed opt-in/opt-out page every high-volume sender already links from its email footer — inline on your own site instead of as a full-page link. Unlike the other elements there is no engine class behind this one: the hosted page is the complete UI, so the element is a thin, validated iframe wrapper. The page, the signed-link model, and the compliance surfaces an opt-out writes are documented in the preference center guide.

3. Quickstarts — one page, five tags

A complete page that registers the bundle and mounts every element. Each value tagged YOUR_… is minted or supplied by your backend — none of them is an API key.
Per-element minimal snippets, for a page that mounts one at a time: <orbit-chat> — the launcher appears in the corner once the tag connects:
<orbit-verify> — start on silent one-tap, fall back to an SMS code entry:
<orbit-video-room> — camera-off, muted entry for a lobby page:
<orbit-click-to-call> — bare surface; a button you own triggers the dial:
<orbit-preference-center> — embed the footer-link page inside your account area:

4. Theming

Each element keeps its engine’s theming mechanism — there is no elements-level token system to learn.
  • Hex-color attribute. <orbit-chat> accepts primary-color="#0EA5A4" (a #RGB / #RRGGBB hex value; anything else falls back to the color configured on your tenant’s native-chat channel). The element forwards the attribute to the same config field the imperative class sets.
  • data-orbit-* CSS hooks. The video room paints its tiles and controls with a data-orbit-video-room attribute on every element it renders, and the verify form does the same with data-orbit-verify hooks — so a plain stylesheet on your page can restyle either without touching shadow roots:
  • Plain CSS on the light-dom hosts. The elements are light-DOM custom elements — no shadow root — so your page’s existing stylesheet reaches them directly. The most useful case is sizing: <orbit-video-room> needs an explicit height, and both iframe-backed elements (<orbit-click-to-call>, <orbit-preference-center>) take width/height attributes that become iframe dimensions.

5. Events and programmatic interop

The elements re-dispatch their engine’s lifecycle events as native, bubbling CustomEvents, so a page with no import statement reacts with a plain addEventListener:
Because the events bubble, a single listener on document.body catches every element’s events — useful when a page mounts several and centralizes analytics. For anything beyond events, drop to the imperative classes from the same package — the elements and the classes are two surfaces over one engine, so the Web SDK reference is the authority for the full option and method list: <orbit-click-to-call> also exposes one method on the element itself — dial(number, crmContext) — so a host page can trigger a call from its own button without importing the class. Calling it before the element mounts logs a warning and does nothing.

6. Troubleshooting

The tag renders nothing and the console shows a warning. Each element warns and refuses to mount when a required attribute is missing or malformed: <orbit-chat> logs the missing widget-id; <orbit-verify> logs the missing to/base-url; <orbit-video-room> and <orbit-click-to-call> log the missing token pair; <orbit-preference-center> logs that src must be an absolute http(s) URL. Open the browser console — the warning names the attribute. The tag is still an unknown element (HTMLUnknownElement). The registration script never ran. With the IIFE bundle, check the <script src="…orbit-elements.iife.js"> actually 200s (view-source and click the URL); with the ESM path, check defineOrbitElements() was called. A tag that upgrades silently but mounts nothing usually means the attribute typo above instead. A Content-Security-Policy blocks the widget. The page needs CSP room for: (1) script-src covering wherever you host orbit-elements.iife.js (self-host keeps this to your own origin), (2) frame-src for the two iframe-backed elements’ origins — https://orbit.devotel.io for the CTI embed and https://app.orbit.devotel.io for the preference center — and (3) connect-src for https://api.orbit.devotel.io plus your own verify-proxy origin for <orbit-verify>. The video room additionally opens a WebSocket to the media URL you pass in server-url — allow its host in connect-src. The <script> tag placement matters less than load order. Elements mount when they connect to the document; if the registration script loads after the browser parses your tags, the tags upgrade then and mount normally. The failure mode to avoid is the reverse — a page that injects tags dynamically without the bundle having loaded — because nothing ever upgrades them. Load the bundle in the page <head> or right after <body> opens and any placement of the tags themselves works. <orbit-video-room> shows a collapsed blank strip. The element is the video container; with no height the tiles have nowhere to paint. Wrap it in a <div style="height: 600px"> or set a height on the tag itself. <orbit-preference-center> frames a blank or broken page. Two causes, in order of frequency: (1) the token on the src is expired or was signed for a different contact — mint a fresh per-contact link (30-day expiry, scoped to exactly one contact); (2) the tenant changed the preference page’s locale path and the src points at a removed URL — copy the current link out of the dashboard’s preference-center settings rather than freezing it in a template. <orbit-click-to-call> fires orbit-expired immediately. The CTI embed JWT has a short lifetime; mint it at page-render time on your backend rather than baking it into a static page, and re-fetch when the event fires.

See also