WooCommerce widget bridge
The plugin configures the standard bridge after a successful store pairing, but themes, consent tooling, caches, CSP, and extensions still require verification. This page explains what the bridge does, so you can reason about it when you have a custom theme, debug a context issue, or audit what leaves your storefront.
What the bridge does
The bridge is a small script (widget-bridge.js) the plugin enqueues on every storefront page. It mounts the chat iframe lazily (on first launcher click, so visitors who never open chat do not download the full chat iframe). The loader, bridge, tracking and any other enabled storefront features can still do work; measure their effect on your own store. The bridge also pushes WooCommerce session state into the widget over postMessage:
- Page context - page type (home / product / category / cart / checkout / policy / support), and on product pages the product ID, title, price, category, and the currently selected variation.
- Cart context - current item count and cart state, re-read on WooCommerce cart-fragment updates so AJAX add-to-cart is reflected without a reload.
- Customer identity when the shopper is logged in - email, display name, WordPress user ID, and a signed
idToken(details below).
How the plugin emits it
On every page load the plugin localizes a configuration object, window.salespilotAiWidget, that the bridge reads. For a logged-in customer it looks roughly like this:
window.salespilotAiWidget = {
storeId: "<your store id>",
widgetKey: "<public widget key>",
appBaseUrl: "https://app.zubbyai.com",
identity: { // omitted entirely for guests
email: "shopper@example.com",
name: "Alex Shopper",
externalId: "42", // WordPress user ID
idToken: "v1.<exp>.<hmac>", // signed server-side, expires in 30 min
loggedIn: true
}
};The bridge then posts two message types into the chat iframe - salespilot:page_context on load and whenever the cart changes, and salespilot:identity for the logged-in customer. Both are posted to the exact Zubby origin only, never *.
The signed identity token (tiered trust)
The widget itself is unauthenticated - anyone could type any email into a browser console. So identity is tiered:
- Signed - the plugin runs server-side in WordPress and knows the logged-in customer for certain, so it mints a short-lived HMAC token (
v1.<exp>.<hmac>, HMAC-SHA256 over the store ID, customer ID, email, and expiry, keyed with the store secret). The SaaS verifies it before treating the identity as real - only then does the AI greet the shopper by name and answer order questions without an order number. - Unsigned or invalid - the claim is discarded for anything sensitive. A spoofed email never unlocks another shopper’s orders or PII.
Guests send nothing
identity block entirely - no email, no user ID. Only page and cart context is forwarded. Tokens expire after 30 minutes and are bound to the exact (store, customer, email) they were signed for.Custom themes and edge cases
The bridge detects page type from WooCommerce’s standard body classes (single-product, woocommerce-cart, etc.) and product data from standard theme markup (h1.product_title, input[name="variation_id"], the .variations selects). Three situations to know about:
- Heavily customized themes - if your theme strips WooCommerce body classes or renders product pages without the standard markup, page context degrades gracefully to
unknown; chat still works, it just loses page-aware smarts. Keeping the standard classes restores it. - Themes that skip
wp_footer- the bridge is enqueued through WordPress’s script pipeline, so a theme must callwp_footer()for enqueued footer scripts to print. That’s a WordPress requirement, not a Zubby one. - Headless WooCommerce (e.g. Next.js + WPGraphQL) - there’s no WordPress page for the plugin to inject into. Embed the hosted widget directly with the
zubby-widgetweb component instead. The WordPress page bridge is not injected into a separate frontend. A WooCommerce workspace does not expose the custom-store signing-secret card: do not copy the custom-store identity recipe or place its Woo API token in client code. Verify a server-side integration with your developer before relying on protected order access. The custom identity guideapplies to workspaces configured as Custom.
Widget visibility controls
The plugin settings expose per-page toggles: the widget is hidden on checkout by default (to keep the payment step distraction-free), can optionally be hidden on the cart page, and can be disabled entirely without deactivating the plugin. Appearance (colors, launcher, texts) is managed centrally in the dashboard’s Widget Designer - not in WordPress.
Check login, expiry, caching, and consent
- In a private guest session, confirm there is no identity block. Then sign in with a controlled test customer and confirm only that customer’s name and order access are available.
- Log out and load a fresh page. The guest must not inherit signed output from a cached logged-in page. Exclude personalized identity output from shared page caching and check both classic and block themes with your caching plugin.
- In a test environment, retry an expired or altered token and check that protected data stays unavailable. Do not disable signature verification to work around an expiry failure; refresh through the trusted server path.
- With your consent tool enabled, check the intended before/after consent behavior and that the bridge initializes once when allowed. Consent timing and identity verification are separate controls.
Support examples should contain only synthetic identities. Never post live idToken values, API tokens, or a real customer’s console output.
Debugging
From the browser console on your storefront:
// Inspect the config the bridge reads on boot
console.log(window.salespilotAiWidget);
// identity present? (undefined for guests - that's correct)
console.log(window.salespilotAiWidget?.identity);If window.salespilotAiWidget is undefined, the plugin didn’t inject - check that the store is connected, the widget is enabled in plugin settings, and you’re not on a page it’s configured to hide (checkout, by default). See Widget not loading for the full playbook.