Web push
activeThis how-to makes a browser reachable on Engage's web push channel. Three things happen: your site gets a push token from Firebase, you hand that token to the Web SDK, and Binoban's service worker displays and tracks whatever arrives.
Before you start
- The Web SDK is loaded on your site and tracking.
- Your Firebase project is set up per Set up Firebase for Binoban push, and you have your VAPID public key.
- Your site is served over HTTPS (or
localhostin development) — the Push API is unavailable otherwise. - You have the
binoban-messaging.jsURL from Binoban (see Step 1).
Step 1 — Load the Binoban service worker
binoban-messaging.js is deployed by Binoban to the infrastructure you
provide, and Binoban gives you its URL — the same arrangement as the Web SDK
bundle itself. You do not build, host, or update this file. If you do not have
the URL, ask your Binoban contact.
What is yours is the service worker the browser registers. A service worker must be served from your own origin and can only control pages within its scope, so keep a worker file at your site root and pull Binoban's logic into it:
// /firebase-messaging-sw.js — served from your own origin
importScripts('YOUR_BINOBAN_MESSAGING_URL')
That single line is the whole worker. importScripts is allowed to load
cross-origin, so Binoban's URL may live on a different host than your site — only
your worker file is bound by the same-origin rule.
Binoban's worker listens for the browser's raw push event and parses the
message itself, so it works without the Firebase messaging library loaded in the
service worker. If you already run Firebase there for your own messages, keep it —
the two coexist, provided you add the guard below.
Tell Binoban's pushes apart from your own
Every message Binoban sends carries source: "binoban" in its data payload.
The backend sets it; you never set it yourself. It is present on every
platform — web, Android, and iOS alike — and it is the marker that keeps
Binoban's push handling and your own from colliding.
Binoban's side is already handled: its worker drops any payload whose source is
not "binoban", so your messages never reach it. Your side is the half you write.
Because Binoban's worker displays the notification itself, any handler of yours
that also displays one will double-display unless it skips Binoban's messages:
// /firebase-messaging-sw.js
importScripts('YOUR_BINOBAN_MESSAGING_URL')
self.addEventListener('push', (event) => {
let payload
try {
payload = event.data ? JSON.parse(event.data.text()) : undefined
} catch {
return
}
// Binoban's own listener already displayed and tracked this one.
if (payload?.data?.source === 'binoban') return
// ...your own notification handling
})
Apply the same guard wherever else you handle incoming pushes:
- Firebase's
onBackgroundMessagein the worker — checkpayload.data?.source. - Firebase's
onMessageon the page. Apushevent reaches the service worker whether or not your tab is focused, so Binoban has already displayed the notification by the timeonMessageruns. An unguarded handler that shows its own notification double-displays foreground pushes.
Step 2 — Ask permission and get a token
Request notification permission, register the worker, and ask Firebase for a token bound to that registration:
import { initializeApp } from 'firebase/app'
import { getMessaging, getToken } from 'firebase/messaging'
const app = initializeApp(firebaseConfig)
const messaging = getMessaging(app)
async function subscribeToPush() {
const permission = await Notification.requestPermission()
if (permission !== 'granted') return
const registration = await navigator.serviceWorker.register(
'/firebase-messaging-sw.js'
)
const token = await getToken(messaging, {
vapidKey: 'YOUR_VAPID_PUBLIC_KEY',
serviceWorkerRegistration: registration,
})
if (token) registerWithBinoban(token)
}
Call this from a user gesture — a "Turn on notifications" button — rather than on page load. Browsers penalise sites that prompt unprompted, and a denied permission cannot be re-requested.
Step 3 — Hand the token to Binoban
One track call registers the browser:
function registerWithBinoban(token) {
Binoban.track('notification_registered', { key: token })
}
Call it again whenever Firebase issues a refreshed token, so Engage always holds the current one.
The Web SDK rewrites this event to bb_notification_registered on the way
out and stamps sdk: "WEB" and the current device_id onto it — that renamed
event is what tells Engage the browser is reachable. See
Push events.
Step 4 — What the worker does from here
Once a browser is subscribed, everything below is handled for you. There is nothing further to write:
- Displays the notification with the title, body, icon, image, and badge from
the payload, honouring
requireInteraction. - Reports delivery the moment it displays — and reports
failedinstead if the browser refuses to show it. - Renders up to two action buttons. Browsers that do not support notification actions (Safari, for example) simply ignore them.
- Reports the click, including which button was pressed and the resolved target URL.
- Opens the target — focusing an already-open tab for that URL if there is one, otherwise opening a new window. A payload with no target falls back to your site.
- Reports dismissals when the user closes the notification.
Step 5 — Verify
- In DevTools → Application → Service Workers, your worker is activated and running.
- In DevTools → Application → Notifications, permission is granted.
- Your next flushed batch contains a
bb_notification_registeredevent carrying the token. - Send a test campaign from the panel; the notification appears, and
bb_notification_deliveredfollows.
Known limits
- There is no
subscribe()helper in the Web SDK. Requesting permission and registering the worker stay yours by design — they are ordinary browser APIs, and when to ask for consent is a decision about your site's UX, not something an SDK should make. customDatais not surfaced on the web. The worker does not read it; use the target URL to carry context instead. It is available on the native SDKs.- Two action buttons maximum on the web, against three on native.
Next steps
- App push — the same job on Android, iOS, and React Native.
- Push events — what gets emitted, and the payload keys.
- Web SDK reference — the full Web SDK surface.
- Activate Engage — what is panel-owned vs. developer-owned.