Browser push (Web Push)
Browser push reaches the same people the site widget already talks to: visitors of your website who explicitly allowed notifications. It is a separate mechanism from the operator notifications you see in the dashboard and in the mobile apps — different storage, different credentials, different audience.
What this release does and does not do. You can enable browser push for a bot, opt visitors in, and send a test notification to one subscribed browser from the dashboard. There is no way to send a browser push from a broadcast, a campaign or a reaction — the only send that exists today is the test send. Set up and verify the channel now; do not plan a mailing on it yet.
Step 1. Install the service worker on your own domain
This is the step without which browser push cannot work at all, and the one that is easiest to skip.
A browser notification is delivered by a service worker, and a service worker can only control the origin that serves it. Our widget script is loaded from our domain, so it cannot install a worker for your site — you have to host the file yourself.
-
Open Widget → Web Push in the dashboard and download
mybot-sw.js. -
Upload it so that it is served from the root of every origin where the widget runs, at exactly this path:
https://example.com/mybot-sw.js https://shop.example.com/mybot-sw.jsThe Web Push settings list the expected address for each of your allowed notification links, so you can open them in a browser and confirm they return the script.
-
Check the file really is served as JavaScript over HTTPS, from the site root, and that it is not rewritten by your SPA fallback or your CDN to an HTML page. A
404, a redirect or anindex.htmlbody all break registration silently. -
Re-upload the file after a platform update the same way you would refresh any other static asset. You never need to reinstall it after changing Web Push settings or rotating keys.
The path matters: the widget only ever registers, or reuses, a worker whose path is /mybot-sw.js. It attaches two public routing parameters (mb_key, mb_api) itself — you do not have to add them.
Registering the worker yourself
If you prefer to control registration, or you want to decide which addresses a notification click may open, register the file from your own code:
navigator.serviceWorker.register("/mybot-sw.js?nav=https://shop.example.com");
The nav parameter is a comma-separated list of exact HTTPS origins (at most 8). The worker checks a click target against its own list and its own origin, independently of the server-side allowlist in Step 2 — neither side trusts the other. When the widget later finds this registration, it keeps your nav value instead of replacing it.
If your site already has a service worker
The widget never replaces a service worker it did not install: replacing yours would break your site, and subscribing through yours would not install the push and click handlers. If another worker already controls the page, opting in simply fails as unavailable. Serve mybot-sw.js from a site that does not yet register its own root-scope worker, or contact support before combining the two.
Step 2. Enable Web Push for the bot
Web Push is configured on the Widget screen, next to the rest of the web channel. Two fields are required:
- VAPID contact — a
mailto:address or an HTTPS URL that push services use to reach you about delivery problems, for examplemailto:help@example.com. - Allowed notification links — one HTTPS origin per line. A notification click can only open a relative address on your site or one of these exact origins. This is a required input, not an optional hardening step: a notification whose link points anywhere else is rejected before it is sent.
Saving for the first time generates a VAPID key pair on the server. The public key is shown in the settings and is the only half a browser ever sees; the private half never leaves the server and is never returned by any API.
Quiet hours (see below) exist in the API but have no field on this screen yet.
Step 3. Let visitors opt in
Consent is deliberately high-friction, and the widget is built so that loading a page can never become an opt-in:
- The browser permission prompt opens only after an explicit visitor click on the widget's notification control.
- A visitor who never asked never sees an error. If the browser does not support push or the permission was already denied, the control is simply hidden until they try once.
- Loading the widget only refreshes an existing consent. It cannot prompt, install a worker or create a new subscription.
- Opting out is durable and survives a reload, even if the browser keeps its own subscription object alive.
Both actions are also available to your own page code once the widget has loaded:
await window.mybot.push.subscribe(); // opens the prompt, then registers the subscription
await window.mybot.push.unsubscribe(); // revokes it server-side and unsubscribes the browser
A subscription is stored with a consent status: subscribed, revoked (the visitor opted out) or expired (the push service reported the endpoint as gone). The dashboard and the API show the browser family, locale, timezone, consent status and timestamps. The push endpoint and its encryption keys are write-only server material: they are stored encrypted and are never returned to an owner, an API client or an MCP tool.
Sending
The Test notification section sends one message to the most recently active subscribed browser. It needs three things at once:
- Web Push enabled for the bot;
- at least one subscription with the
subscribedstatus; - one published document of type Push in Content studio. The test send references an exact published version, exactly like a popup or a campaign does.
The send is asynchronous: the dashboard receives a message id, and delivery happens in the background. Everything else that a campaign send would need — audience selection, scheduling, stop control — is not wired to any user-facing screen in this release.
Notification content limits
Every limit below is a hard rejection before anything leaves the server, not a truncation.
| Field | Limit |
|---|---|
title | required, 1–120 characters |
body | required, 1–512 characters |
tag | optional, ≤128 characters |
actions | at most 2; each action id and title 1–64 characters, no duplicate ids |
icon_url, image_url | HTTPS only, no user info, no fragment |
navigation | a relative path starting with /, or one of the exactly allowed origins |
data | only the keys action, campaign_id, content_id, experiment_id, message_id; each value ≤256 characters |
| rendered payload | ≤3 KiB in total |
Two more rules are worth knowing. A data value that looks like a credential — it contains bearer , secret, password, token= or an @ sign — is refused, so a profile value or an access token cannot be smuggled into a notification. And a payload that tries to carry its own receipt_signature is refused outright: that value is runtime metadata the server injects at delivery, never content.
Protocol-relative (//host/path) and backslash forms of a link are treated as foreign origins, not as relative paths, on both the server and the worker.
Quiet hours
Quiet hours are recurring local intervals in which delivery is deferred, not dropped: the message waits and goes out at the first allowed moment. They are evaluated in the recipient's timezone (the one recorded with the subscription, falling back to the bot's), and an interval whose end is earlier than its start continues overnight. A moment that falls into a daylight-saving gap is rounded forward to a real instant rather than discarded.
Quiet hours are part of the Web Push configuration object and can be set through the REST API (quiet_hours on the config), but the dashboard has no editor for them yet.
Delivery, retries and receipts
- A message is picked up by a background runner, leased while it is being delivered, and retried up to 8 attempts or 24 hours, whichever comes first.
- A
429from the push service is retried according to itsRetry-After, capped at 24 hours. Timeouts and5xxanswers are retried with a jittered delay. Any other4xxis terminal. - A
404or410means the browser subscription is gone: it is markedexpiredand never retried again. - The service worker posts back two receipts:
clickwhen the visitor opens the notification, andclosewhen they dismiss it. Each receipt carries a signature bound to that one message and that one subscription, so a receipt cannot be forged or replayed across messages. - The report on the settings screen counts subscriptions by status and notification opens. Internally the timeline records
accepted,sent,failed,expired,clickedandclosedfacts, which also feed the customer event timeline on People.
Provider response bodies are never stored — a failed delivery keeps only a closed reason code.
Rotating VAPID keys
Rotation is destructive and asks twice. The first call answers 409 with the number of currently active subscriptions, which the dashboard shows in the confirmation dialog. Confirming generates a new key pair and expires every existing subscription; the response reports how many were expired. Every visitor has to opt in again, because a browser subscription is permanently bound to the key it was created with.
The widget notices this too: a browser subscription created with a retired key is discarded and recreated on the visitor's next explicit opt-in, instead of being silently kept as dead weight.
Security boundaries
Worth knowing when your security team asks:
- MyBot only ever posts to a public HTTPS endpoint on port 443. Loopback, private, link-local, multicast and carrier-grade NAT addresses are refused, and if a hostname resolves to several addresses, all of them must be public.
- The hostname is re-resolved and re-checked on every delivery, not once at registration, and the connection is pinned to the address that was checked. Redirects are never followed and the response body is bounded.
- Endpoints are deduplicated by a keyed, tenant-scoped digest, so one bot can never probe whether a browser is subscribed to another bot.
- Browser-facing routes live outside the
/apisurface, on the widget's own trust boundary: each one re-checks the signed widget key, the widget session and the exact page origin, and shares the widget's rate budget.
Data and retention
Browser push subscriptions and messages fall into the push retention category with a default retention of 180 days. They participate in subscriber merge, personal-data export and erasure like any other customer data.
Browser support and troubleshooting
Support is decided by the browser: it needs notifications, service workers and the Push API at once. Current desktop and Android versions of Chrome, Edge and Firefox qualify. On iOS, Safari only offers web push for a site the visitor has added to the Home Screen.
If nothing arrives:
- Open
https://your-site/mybot-sw.jsin a browser and confirm the script is returned — not a404, a redirect or an HTML page. - Check that the page origin is saved as an allowed origin for the widget and that the notification link points at an allowed notification link.
- Confirm the visitor's browser really granted permission, and that no other service worker controls the page.
- Re-check after a key rotation: everyone has to opt in again.
- If the test send is unavailable, you are missing either a subscribed browser or a published Push content document.
API and MCP
Seven owner routes are available with a personal access token — see REST API and tokens. Reads need the web_push:read scope and the bot's analytics right; writes need web_push:write and the reactions right. MCP exposes three read-only tools and deliberately no enable, rotate or delete tool — see MCP.
What's next
- Site widget — install and verify the widget itself.
- Content studio — write the Push document a send references.
- Customer SDK — the same content and chat inside a mobile app.