Guides
Notifications
BrowserHive tells you when something needs you or went wrong: an agent asked for help, a vault fill waits for your approval, a session crashed, tools keep failing, or BrowserHive itself is degraded. Notifications are stored in the database, so they survive reloads and restarts, and they appear in the dashboard’s bell, as toasts and on the Notifications page.
BrowserHive can also put them on your phone: through your own Telegram bot, a Discord webhook or bot, an ntfy topic or a webhook of your own. And you can answer from there: Approve, Reject or Mark resolved right in the chat. This page explains what produces a notification, how one changes over its life, how to set up each channel, how answering from your phone works, and what leaves your machine.
- Channels: set one up in two minutes
- Answer from your phone: act buttons on Telegram, Discord and ntfy
- Daily digests and anomaly alerts: a summary on your schedule, and a heads-up when something is off
- Public address: links that open on your phone
- Screenshots, self-destruct, startup channels, the delivery log
- What leaves your machine
What BrowserHive notifies about
Section titled “What BrowserHive notifies about”| What happened | Kind | Category | Severity |
|---|---|---|---|
An agent called request_attention (human takeover) |
attention.requested |
needs you | warn |
| A vault fill waits for your confirmation (vault) | vault.confirm |
needs you | warn |
| A session crashed | session.crashed |
problems | error |
| A session was reaped because its lease expired | session.reaped |
problems | warn |
| Tools failed (grouped per session: “shop · 12 tool errors”) | tool.errors |
problems | warn |
| A subsystem is degraded (for example the retention sweep failed) | system.degraded |
system | error |
| A notification channel keeps failing | channel.broken |
system | error |
| A channel’s scheduled digest is due (digests) | digest.daily, digest.weekly |
reports | info |
| A channel’s hourly check found something off | report.anomaly |
reports | warn (error while degraded or at capacity) |
Routine events are deliberately silent: a session opening, a page visit, a clean close. Agents cannot send notifications themselves: every notification comes from something BrowserHive observed. The request_attention tool is how an agent reaches you.
A notification has a life
Section titled “A notification has a life”A notification keeps its identity while the thing it announces changes. When you resolve an attention request, reject it, or it times out, the same notification moves to resolved or expired instead of a second one appearing. The dashboard shows that outcome as a small pill on the row (resolved, expired, or closed when the agent stopped waiting) and closes the toast if it is still on screen. The row keeps its place in the list. A growing group of tool errors updates its count in place.
Every change is a new revision of the notification’s message. Each revision is complete, so whoever shows it never has to merge changes. That is also what lets a chat message be edited in place later, silently: only a new notification makes noise.
Notifications from before this release keep working; they get the new fields, derived from what they already recorded, and nothing is invented for them.
The message contract
Section titled “The message contract”Every notification is also a NotificationMessage: a small, versioned JSON document with a title and summary, structured blocks (text, facts, lists, tables, images, code), up to five buttons, and what it is about (session, tool, error code). Links in it are dashboard paths. BrowserHive owns this contract; every channel renders it and none reads BrowserHive’s internals. The JSON Schema is published at notification-message.schema.json, so you can build your own consumer from the webhook channel.
Before a message is stored or sent, BrowserHive removes known secrets and credential-shaped text from every field and strips query strings from URLs, the same redaction the logs get (security). A channel can be set to carry less: only titles and facts, or only counts.
How delivery to other apps works
Section titled “How delivery to other apps works”Delivery is designed so that nothing is silently lost and nothing waits on a slow chat service:
- An outbox in the database. When a notification is created or changes, the jobs that deliver it to each channel are written in the same database transaction. A worker sends them afterwards. If BrowserHive stops mid-way, it picks up at the next start. This guarantees at-least-once delivery: an edit or a delete can safely be repeated, and in the rare case of a crash in the middle of sending a new message, that message can arrive twice.
- Edits instead of spam. When a notification changes, its chat message is edited in place, at most once every 3 seconds. Edits never make a sound.
- Retries. A failed send is retried with growing pauses, honouring the platform’s “retry after”. It gives up after 8 attempts or 24 hours.
- A circuit breaker. After 5 failures in a row, the channel is marked broken, you get an in-app notification about it, and its deliveries pause. A failing channel is never reported as a BrowserHive degradation: that report would be sent through the same failing channel.
- Catching up. After an outage only the latest state of each notification is sent, and a pile of routine updates collapses into one message that says how many you missed.
- A log for every decision. Each delivery is logged, including the ones a channel’s rules filtered out and why, so “why didn’t I get it?” always has an answer. The log is kept for 30 days.
- Messages that clean up after themselves. A channel can delete its messages after a time you choose per category, or once they are resolved. BrowserHive does the deleting, because no chat platform offers a timer for bot messages. The default is to keep everything.
Your own accounts, no servers: BrowserHive never runs a relay or a shared bot. You create your own Telegram bot, Discord webhook or ntfy topic, and every connection goes out from your machine. Tokens stay in environment variables; BrowserHive stores only the variable names, so a database backup never contains a token.
Channels
Section titled “Channels”A channel is one place notifications go: a Telegram chat, a Discord channel, an ntfy topic or a URL of yours. Add one in the dashboard under Notifications → Channels → Add channel. The wizard walks through five steps:
- Platform. Telegram, Discord, ntfy or Webhook.
- Credentials. Pick the name of an environment variable that will hold the token, such as
BH_TELEGRAM_TOKEN(any name that does not start withBROWSERHIVE_, which configuration keys use). The wizard shows the exact line for how you run BrowserHive (a shell, systemd, Docker) and a live set ✓ / missing ✗ check. Set the variable and restart BrowserHive; the wizard keeps your draft across the restart. - Connect. The platform-specific part, below.
- What to send. Pick a preset (Needs me now, Problems, Wrap-ups, Everything or Daily digest), set up reports (a digest, the time zone, anomaly alerts), or open Advanced for minimum severity, session name patterns, harness, quiet hours, the content level, screenshots and self-destruct.
- Preview and test. See the message exactly as it will look (drawn from the same code that sends it), save, then Send test. The test message has an Open dashboard button: tap it on your phone to check that links reach you (public address).
Each channel card shows its status, the last delivery and the last 24 hours (sent, failed, filtered). You can pause, resume, edit, duplicate, delete and test it there. There is no limit on channels; every matching channel gets its own copy.
Secrets are never stored: a channel holds the name of the environment variable, BrowserHive reads the value when it starts, and the dashboard only ever says whether it is set. Changing a token is a change to the environment plus a restart.
Telegram
Section titled “Telegram”- In Telegram, open a chat with @BotFather, send
/newbot, choose a display name and a username ending inbot. BotFather answers with a token like123456789:AA…. - Put it in a variable where BrowserHive runs, for example
export BH_TELEGRAM_TOKEN='123456789:AA…', and restart BrowserHive. - In the wizard’s Connect step, tap the one-time link (or scan its QR code with your phone). It opens a chat with your bot and sends
/start. BrowserHive waits up to two minutes for it and fills in the chat by itself. For a group, use Add to a group instead, pick the group, and the bot posts there. For a forum topic, send the start link inside that topic.
Messages are Telegram Rich Messages: a heading with the severity or outcome, the summary, the screenshot, the facts as a compact table, real tables, collapsible quotes and code blocks. Page text never turns into a mention or a command. Buttons sit below the message: links, and with act buttons on, Mark resolved and Reject (or Approve and Deny for a vault fill), coloured green and red. When a request is settled, the message is edited in place, silently: the buttons disappear and the heading shows who answered.
If Telegram ever refuses a Rich Message (a formatting it rejects, or a self-hosted Bot API server that does not know them yet), BrowserHive sends the same notification as a classic HTML message instead, so nothing is lost; messages already sent that way keep being edited that way.
Telegram lets a bot delete its own messages for 48 hours only, so self-destruct timers on a Telegram channel go up to 47 hours. Telegram’s own auto-delete timer (chat settings → Auto-delete messages) is a good backstop.
Discord
Section titled “Discord”- In Discord, open the channel’s settings: Edit Channel → Integrations → Webhooks → New Webhook. Name it, optionally set an avatar, then Copy Webhook URL.
- Put the URL in a variable, for example
export BH_DISCORD_WEBHOOK='https://discord.com/api/webhooks/…', and restart BrowserHive. The URL is the secret: anyone who has it can post to your channel. - In the wizard, choose Webhook mode. There is nothing else to connect.
Messages are an embed: a coloured bar by severity, the facts as fields, the screenshot as the embed image, and link buttons below. Edits are silent; deletes work at any age.
Webhook or bot? A Discord channel uses one mode, and you can switch later without losing its rules. Webhook mode takes thirty seconds and needs no connection, but its buttons can only open links. Bot mode takes about three minutes and is the only way to press Approve or Reject right in Discord; BrowserHive then keeps one outbound connection to Discord (the gateway) while it runs. The wizard’s What’s the difference? panel shows both message styles side by side.
Bot mode, step by step:
- Create the bot. Open discord.com/developers → New Application, name it (for example “BrowserHive”) → Bot tab → Reset Token. Discord shows the token once: copy it and put it in a variable where BrowserHive runs, for example
export BH_DISCORD_BOT_TOKEN='…'(any name that does not start withBROWSERHIVE_), then restart BrowserHive. - Make it private (recommended, so nobody else can add your bot to their server). The order matters: first Installation tab → Install Link → set it to None → Save; only then Bot tab → turn Public Bot off → Save. If you turn Public Bot off while an install link is still set, Discord refuses with “Private application cannot have a default authorization link. Please check that the default authorization link is set to None in the installation tab.”: set the install link to None, save, and try again.
- Leave every Privileged Gateway Intent off (Bot tab). BrowserHive needs none: button presses arrive without them.
- Invite the bot to your server. In the wizard choose Bot; in Connect, Invite the bot opens Discord with the right link: scope
botand exactly the permissions BrowserHive needs, View Channels, Send Messages, Embed Links, Attach Files. Pick your server and Authorize. The link works for you even though the bot is private, because you own the application. By hand instead: Developer Portal → OAuth2 → URL Generator → scope bot only (notapplications.commandsor anything else), Guild Install if asked, the four permissions above → open the generated URL → pick your server → Authorize. Read Message History is not needed (adding it does no harm). - Pick the channel. Back in the wizard press Refresh, then pick the server and the channel from the lists (the bot fetches them). Without the wizard (a startup channel), you need the channel id: Discord Settings → Advanced → Developer Mode on, then right-click the channel → Copy Channel ID, and pass
channel=<id>. - Link your Discord account. The bot posts a message with a This is me button in that channel. Press it within two minutes: your account becomes the first one allowed to answer (allow-list). The bot deletes that message afterwards. On a startup channel, list the user ids in
allow=instead (Developer Mode → right-click your name → Copy User ID).
The bot sends the same embed as a webhook, with Mark resolved/Reject buttons when act buttons are on. The channel card shows the gateway connection: connected, reconnecting (BrowserHive retries with growing pauses and resumes where Discord allows) or offline with the reason, such as a refused token.
Troubleshooting Discord bot mode
Section titled “Troubleshooting Discord bot mode”- “Missing Access” or “Missing Permissions” when sending (the delivery log shows
Discord 403): the bot is not in that channel or lacks a permission there. It needs View Channels and Send Messages to post, Embed Links for the embed and Attach Files for screenshots; a channel’s own permission overrides can take them away even when the server role has them. Re-run Invite the bot, or give the bot’s role those permissions on that channel. - “Unknown Channel”: the channel was deleted, or the id belongs to another server the bot is not in. Pick the channel again.
- The card says offline: “Discord refused the bot token.” The token was reset or mistyped. Reset it in the Developer Portal (Bot → Reset Token), update the variable, restart BrowserHive.
- The card says reconnecting. Discord or the network dropped the connection; BrowserHive reconnects by itself and resumes the session where it can. Presses made in the meantime fail on Discord’s side (next point).
- “This interaction failed” after pressing a button. Discord needs an answer within 3 seconds. BrowserHive answers at once (and finishes slow commands in a follow-up), so this means BrowserHive was not running or not connected at that moment. Unlike Telegram, Discord does not keep the press: press again once the card shows connected.
- “Invalid Form Body” in the delivery log names the field Discord refused; please report it as a bug.
ntfy is a free, open-source push service with apps for Android and iOS. You can use the public server ntfy.sh or host your own.
- Install the ntfy app on your phone.
- In the wizard, keep the server (
https://ntfy.sh) or enter your own, and keep the suggested random topic (for examplebh-7f3kq9x2) or type one. On a public server the topic is the password: anyone who knows it can read your notifications, so keep it long and random. You can also keep it in a variable (topicfromBH_NTFY_TOPIC). - Scan the QR code with your phone (or tap Subscribe in the app and enter the server and topic).
- For a protected server or topic, create an access token in ntfy and put it in a variable such as
BH_NTFY_TOKEN.
Priority follows severity (a critical notification is urgent). Buttons are “view” actions (at most three); with a reply topic they can also answer. A revision replaces the notification on the phone; a self-destruct deletes it. On ntfy.sh, attachments (screenshots) are stored on the public server for three hours and their links are not documented to be private: for screenshots, a self-hosted ntfy is the better choice. A self-hosted server without an attachment cache refuses uploads; BrowserHive then sends the text alone.
Webhook
Section titled “Webhook”The webhook channel POSTs the message contract itself as JSON, so you can build your own consumer (Home Assistant, n8n, a script):
{ "schema": 1, "event": "notification", "op": "send", "delivered_at": 1790642672932, "channel": { "id": "nc-…", "name": "ops" }, "links": { "take-over": "https://bh.example.net/sessions/…?live=1&takeover=1" }, "local_links": false, "message": { "schema": 1, "id": "n-…", "revision": 1, "kind": "attention.requested", "…": "…" }}A revision is POSTed again with op: "edit" and a higher message.revision; keep the highest. When you set a signing secret (secret from a variable such as BH_WEBHOOK_SECRET), each request carries X-BrowserHive-Timestamp and X-BrowserHive-Signature: sha256=<hex>, the HMAC-SHA256 of the raw body with your secret. Only http: and https: URLs are accepted, redirects are followed only on the same scheme and host, and a private address (your LAN) is allowed with a warning, because the request comes from inside your network.
Answer from your phone
Section titled “Answer from your phone”With act buttons on, a notification that waits for you carries buttons that act, not just links: Mark resolved and Reject for an attention request, Approve and Deny for a vault fill. Press one in the chat, and BrowserHive does exactly what the same button in the dashboard does, then edits the message: the buttons disappear and it says who answered and when (“Resolved on Telegram by 123456789 after 42 s”). Everything else (take over, open the live view) stays a link.
Act buttons are off by default. Switch them on per channel in the wizard’s What to send → Answer from the chat, or with actButtons=true on a startup channel.
| Platform | What it needs | How a press reaches BrowserHive | Who may press |
|---|---|---|---|
| Telegram | nothing more | BrowserHive asks Telegram for new events (long polling), one connection per bot | the people on the channel’s allow-list |
| Discord | bot mode | the bot’s gateway connection | the people on the allow-list |
| ntfy | a reply topic | the phone posts to the reply topic; BrowserHive subscribes to it | whoever can read the notification topic |
| Webhook | a receiver that calls the API | your receiver | whoever holds the API token it uses |
Every connection goes out from your machine; nothing needs a public address. Presses made while BrowserHive is stopped are handled at the next start when the platform kept them (Telegram keeps them for a day, ntfy.sh for 12 hours), and refused when the request has stopped waiting in the meantime. On Discord a press while BrowserHive is stopped shows “This interaction failed”.
Who may answer
Section titled “Who may answer”A button in a chat is a remote control for your agent’s browser, so every press is checked before anything runs:
- The allow-list. Only the Telegram or Discord accounts on the channel’s allow-list (Allowed people) may press. By default that is the person who connected the chat in the wizard: the one who sent
/starton Telegram, or pressed This is me on Discord (the wizard then shows “Connected as”). Anyone else who presses is told, privately (a Telegram notice or a Discord message only they see), that they are not allowed yet and where the admin adds them, with their own id. Their press appears under Notifications → Actions as refused, with Allow this person: one click (after a confirmation) adds them, and their next press works. You can also add people by numeric user id under Answer from the chat → Allowed people (Telegram: the id from the refusal; Discord: Developer Mode on, right-click the person → Copy User ID). A startup channel’s list is its allow=parameter. - Single use. Each button carries a one-time command token (
bh1:and 11 random characters). It works once, for 24 hours, only in the chat it was sent to, and only while the request still waits. BrowserHive stores only a hash of it. - The same rules as the dashboard. The press runs through the same code as the dashboard’s resolve buttons. The agent learns that it was answered from Telegram, Discord or ntfy, but never your chat identity.
- An audit. Notifications → Actions lists every press of a real button: when, which channel and notification, which button, who pressed it and what happened (done, not allowed, already used, expired, the request had already stopped waiting, pressed in another chat, act buttons off, or failed with the reason). Presses of buttons BrowserHive never created are only counted, so a stranger cannot fill the list. The audit is kept like the other audit records (90 days by default).
ntfy: a second topic for answers
Section titled “ntfy: a second topic for answers”ntfy has no bot and no way to receive a button press, so BrowserHive uses a second, private topic, the reply topic. Each act button is an ntfy http action: tapping it makes your phone post the button’s one-time token to the reply topic on your ntfy server. BrowserHive subscribes to the reply topic, acts, and replaces the notification on your phone with its new state. Android and iOS both support these buttons. At most three buttons fit on an ntfy notification: the answers come first, then the links.
Set it in the wizard’s Connect step (a random name is suggested, like the main topic), or reply=bh-reply-… (or reply=env:NAME) on a startup channel. The reply topic must differ from the notification topic. If reading it needs a token, name its variable in replyToken (the channel’s own token is used otherwise).
Security. ntfy notifications carry no user identity, so there is no allow-list: whoever can read the notification topic can press its buttons. Keep the notification topic private: a long random name on ntfy.sh, or access control on your own server. On a self-hosted server, the tidy setup is to let everyone write to the reply topic but not read it, and let BrowserHive read it with a token:
ntfy access everyone bh-reply-7f3kq9x2 write-onlyntfy access browserhive bh-reply-7f3kq9x2 read-onlySomeone who learns only the reply topic can post to it (BrowserHive ignores anything that is not a live token), repeat a token that was already used (refused) and see tokens after they were used. They cannot guess a live token (66 random bits) and so cannot act. Putting an access token into the buttons instead would hand a write token to everyone who can read the notification topic, so BrowserHive does not do that.
Webhook: answer from your own receiver
Section titled “Webhook: answer from your own receiver”With act buttons on, the webhook channel sends the notification’s act actions unchanged in the message contract, for example:
{ "kind": "act", "id": "reject", "label": "Reject", "style": "danger", "command": { "op": "attention.resolve", "args": { "request_id": "a-…", "decision": "reject" } }, "confirm": "Reject this request? The agent is told it was rejected.", "fallback": { "label": "Open in BrowserHive", "path": "/sessions/…?live=1" } }There are no tokens in them and BrowserHive opens no callback endpoint. Your receiver answers through the REST API with an operator API token that has the right scope (attention:resolve, or vault:confirm for vault fills):
curl -X POST https://browserhive.example.net/api/v1/attention/a-…/resolve \ -H "Authorization: Bearer $BH_API_TOKEN" -H 'Content-Type: application/json' \ -d '{"decision":"reject"}'# vault fills: POST /api/v1/vault/confirm/{request_id}/resolve {"decision":"approve"|"deny"}Public address
Section titled “Public address”Links in a notification (“Take over”, “Open session”) must open somewhere your phone can reach. By default they point at this computer (http://127.0.0.1:9876/…) and are labelled Open on this computer; they work on the machine running BrowserHive and nowhere else.
Set publicUrl to the address where you made the dashboard reachable, and every link becomes publicUrl + path:
browserhive --admin --publicUrl https://browserhive.example.net# or BROWSERHIVE_PUBLIC_URL=https://…, or "publicUrl" in browserhive.config.jsonBrowserHive provides no tunnel or proxy; use what you already have:
- Tailscale (nothing exposed to the internet). Install Tailscale on the computer and your phone, then either browse to the machine’s Tailscale name directly (
--host 0.0.0.0 --auth token --publicUrl http://my-box.tail1234.ts.net:9876) or let Tailscale proxy it with HTTPS:tailscale serve --bg 9876and--publicUrl https://my-box.tail1234.ts.net. - A reverse proxy with your domain (Caddy, nginx, Traefik) forwarding to
127.0.0.1:9876, ideally behind an access layer such as Cloudflare Access or your proxy’s own login. - Cloudflare Tunnel (
cloudflared tunnel --url http://127.0.0.1:9876) with Cloudflare Access in front.
The host of publicUrl is trusted automatically: you do not need to add it to allowedHosts, and the CSRF check accepts its origin even when your proxy rewrites the Host header to the upstream address. Links never carry a token; opening one still needs the dashboard login. BrowserHive warns when publicUrl is plain http: on a host that is not your own machine.
Checking it. The System page and browserhive doctor fetch <publicUrl>/health and compare an id that changes at every start:
| Result | Meaning |
|---|---|
| ✓ Points to this BrowserHive | The address reaches this instance. |
| ✗ Points elsewhere | Another server (or another BrowserHive) answered. |
| ! Behind a login | A login page or an access proxy answered, so it cannot be confirmed from here. That is expected with Cloudflare Access. |
| ! Not reachable from this machine | Nothing answered from here. It may still work from outside, for example behind a router without hairpin NAT. |
The final proof is the Open dashboard button of a test message, tapped on your phone.
Daily digests and anomaly alerts
Section titled “Daily digests and anomaly alerts”A channel can also get reports: a summary on a schedule you pick, and a heads-up when something looks off. Set them in the wizard’s What to send step, under Reports, or pick the Daily digest preset (a digest every morning at 09:00 and anomaly alerts, nothing instant). A channel receives its reports whatever its categories: the schedule is how you ask for them. BrowserHive itself keeps every report too, and can make them with no channel at all: see Reports in BrowserHive.
The digest
Section titled “The digest”Every day (09:00 unless you pick another time) or every week (Friday at 17:00 unless you pick another day and time), on the 24-hour clock. “Every day” includes the weekend; tick Weekdays only to get it Monday to Friday, and Monday’s digest then covers the whole weekend. The digest covers the period that ends at its time: the last 24 hours, or the last seven days, weekend included. It says, in numbers:
- sessions started and live now; tool calls, errors and the error rate (with the previous period’s rate);
- attention requests: how many, how many were answered and the median wait, how many timed out, how many wait now;
- vault fills and how many failed; blocked requests and the most blocked pattern;
- the slowest tool (its p95, against the previous period), the top errors, and open problems;
- tool calls per hour as a small chart (text bars in chats; the numbers themselves in the webhook payload), and a table per harness;
- a link to the Overview for exactly that period.
Nothing happened, nothing sent. A period with no session, tool call, attention request, vault fill, blocked request or open problem sends no digest. The delivery log still records it, as not sent · nothing happened in the period, so you can tell a quiet day from a broken channel.
Send a digest now. The channel card (and the channel page) has Send now: a preview of the real digest for the period that ends now, drawn exactly as your phone will show it, and a button to send it on demand. The schedule is not touched. POST /api/v1/channels/{id}/digest does the same.
Time zones
Section titled “Time zones”Each channel has a time zone, and it is BrowserHive’s own zone unless you pick another (the picker is searchable: type a city). Digest times and quiet hours both follow it, so “09:00” means 09:00 on your wall clock, before and after daylight saving time. On the night the clocks go forward, a time that does not exist (02:30 on some nights) fires at the same wall time an hour later; on the night they go back, a time that happens twice fires once. The days around a change are 23 or 25 hours long, and the digest covers them whole.
If you move BrowserHive to another machine, a channel without a zone of its own follows the new machine’s zone.
Late digests
Section titled “Late digests”BrowserHive is often a daemon on a laptop, so it may be off at 09:00. When it starts again, it sends the most recent digest it missed, marked late (“Sent late: BrowserHive was not running at 09:00”), and says how many earlier digests it skipped (“2 earlier digests were skipped while BrowserHive was off”). It sends only that one: a long weekend does not arrive as a burst of stale messages, and the Overview covers the rest. The delivery log shows a late pill on such a digest. Each window is sent once, even across crashes and restarts; changing a digest’s time starts the new schedule from the change, so an edit never causes a late digest.
That one late digest is the whole catch-up: the skipped periods are never sent later, and they are not folded into the next digest.
Quiet hours. A digest is sent at the time you chose even if it falls inside the channel’s quiet hours, but silently there (no sound or vibration).
Anomaly alerts
Section titled “Anomaly alerts”Tell me when something looks off checks every hour, on the last hour of activity, and stays silent unless a check crosses its threshold:
| Check | Alerts when (default) | Clears when |
|---|---|---|
| Many tool calls failing | 20 % or more of tool calls failed, with at least 20 calls | below 10 %, or under 10 calls |
| An attention request waiting too long | a request has waited 30 minutes | no request waits that long |
| Sessions at the limit | live sessions reach maxSessions |
below 90 % of it (at least one below) |
| A spike in blocked requests | 3× the usual hourly count of the day before, and at least 50 | below half of both |
| BrowserHive itself degraded | an error-level problem is open on the System page | it is resolved |
A crossing sends one alert listing every check that is off, the new ones first. When a check clears while others stay, the same message is updated silently; when everything is back under its threshold, it becomes Back to normal, silently by design: good news edits the alert in place and rings nothing. Because each check clears only well below where it fires, a value hovering around a threshold does not flap. During the channel’s quiet hours no check runs; the first check after them tells you what is still off. Each threshold can be changed, or a check switched off, in Advanced → Anomaly checks (anomaly.* on a startup channel).
What a report contains, per content level
Section titled “What a report contains, per content level”| Level | Digest | Anomaly alert |
|---|---|---|
counts |
Numbers only: sessions, calls, errors, attention, vault fills (with how many failed), blocked requests, open problems (how many), the chart | The checks, their values and thresholds |
titles (default) |
Adds tool names, error codes, harnesses, vault results, degradation codes, the top blocklist pattern and the tables | Adds the degradation codes and the sessions whose requests wait |
full |
Adds degradation messages and the most blocked domain | Adds the degradation messages |
Everything in a report passes the same redaction as any notification.
Reports in BrowserHive
Section titled “Reports in BrowserHive”Every report also lands in BrowserHive, once per period, in full (the dashboard is your own screen, so it is not cut to a channel’s content level):
- In the bell and the inbox. A digest arrives quietly: already read, so it never raises the badge, and it never pops up. An anomaly alert counts toward the badge and pops up like a System notification (switch System off in the toast preferences to keep them in the bell only); its “Back to normal” edit is silent and closes the pop-up. The inbox’s Reports chip shows only digests and anomaly alerts; a report opens its report page.
- Once, not once per channel. Channels on the same schedule share one copy: same frequency, weekday, time and time zone, covering the same window. Two channels at 09:00 Berlin get one copy; a channel at 09:00 Berlin and one at 08:00 London get two, because each is written in its own zone. A digest you send with Send now has its own copy. An empty period has none.
- Notifications → Reports keeps the history: every digest and anomaly alert, even after you dismiss it from the inbox, for 90 days. Filter by kind, by where it went (a channel, or BrowserHive only) and by period. A report page draws the digest natively (facts, the chart, tables), says which window it covers and in which zone, marks it late or on demand, lists the channels it reached and how each delivery went, and has Open Overview for this period.
- Without any channel. At the top of the Reports tab, Reports in BrowserHive has its own digest (off by default; every day, optionally weekdays only, or every week; a time; a time zone, BrowserHive’s own unless you pick one) and Tell me when something looks off (off by default, with the default thresholds). They follow the same rules as a channel’s (late once, never empty) and share periods with channels on the same schedule. Changing them needs the
channels:writepermission.
Screenshots
Section titled “Screenshots”A notification can carry a screenshot of what the agent was looking at. Screenshots are off by default and switched on per channel and category (in the wizard’s Advanced step). They are taken for three things only:
- An attention request, CAPTCHA hand-offs included: a picture of the page when the agent asked for help.
- A vault fill waiting for confirmation: the login page and the site being filled, taken before the fill starts (the fill waits for your approval). BrowserHive never takes a screenshot during or after a fill, or while a secret is being typed.
- A crashed session: its last stored screenshot, if it has one.
Screenshots need the content level full, and BrowserHive never takes them when recordToolResults is none. Mask form fields (on by default when you enable screenshots) blacks out inputs, text areas and menus before the picture is taken; a crash’s stored frame cannot be masked, so a masking channel gets no crash screenshot. Screenshots are JPEG files in the data directory, readable only by you, and are removed after 7 days.
Self-destruct
Section titled “Self-destruct”No chat platform lets a bot set a timer on a message, so BrowserHive deletes its messages itself:
- Delete after a time you choose per category (for example: needs-you after 2 hours, problems after 1 day, reports never). The default is never.
- Delete when resolved, per category, off by default: the message goes away once the request is resolved.
The deadline is stored with the message, so a deletion that fell due while BrowserHive was stopped happens at the next start. Telegram only allows deleting within 48 hours; a Telegram message that became older while BrowserHive was off is logged as could_not_delete: too_old. Deleting removes the message for everyone in the chat, but it cannot take back a lock-screen preview someone already saw.
Startup channels
Section titled “Startup channels”For a server or a container you can declare channels on the command line; they exist from the first start without any clicking. Repeat the flag for several channels:
export BH_TG_TOKEN='123456789:AA…'browserhive --admin \ --notificationChannel "telegram:name=phone,token=env:BH_TG_TOKEN,chat=123456789" \ --notificationChannel "ntfy:name=pager,topic=env:BH_NTFY_TOPIC,categories=needs-you+problems,min=warn"- Secrets are always written
env:NAME. A token typed into the flag is refused (exit 64), because other users of the machine can read process arguments. - Rules use the same names as the dashboard:
categories,min,sessions,harness,content,quiet=22:00-07:00,tz(the channel’s time zone),digest=daily@09:00,digest=daily:weekdaysordigest=weekly(Friday at 17:00;weekly:mon@08:30for another day and time),anomaly=onwithanomaly.errorRate=10and the otheranomaly.*thresholds,ttl.needs-you=2h,deleteWhenResolved,images,maskImages,actButtons=trueandallow=123456+789012(the Telegram or Discord user ids allowed to answer; a startup channel has no setup flow, so name them here). A Discord bot isdiscord:name=ops,mode=bot,token=env:BH_DISCORD_BOT_TOKEN,channel=<channel id>; an ntfy reply topic isreply=…. Every parameter is listed in the command-line guide. - The flag has no environment-variable or config-file spelling. Startup channels appear in the dashboard with a from startup badge. You can pause them there, but you edit them by changing the flag and restarting. A startup channel whose name a dashboard channel already uses stops the start with an error, so neither silently wins.
Delivery log
Section titled “Delivery log”Notifications → Delivery log lists every send, edit and delete, live: time, channel, notification, revision, status, attempts and how long the platform took. A digest or anomaly alert also shows the period it covers, and a late or on demand pill. Filter by channel, status, operation or kind. Open a row to see the notification’s journey on every channel and the message exactly as that channel was shown it. Every row that was not delivered says why in a sentence: filtered by the channel’s rules, quiet hours, the channel was paused, the platform refused the token, the message had been deleted in the chat, and so on. The same answers are in GET /api/v1/channels/deliveries.
From a terminal:
browserhive channels list # status, target, variables, answers, next digest, last delivery, 24 h countsbrowserhive channels test phone # a real test message; exit 1 when the platform refused itbrowserhive channels preview phone --sample vault-confirm # the request a send would make; sends nothingbrowserhive channels preview phone --sample digest # a sample digest at the channel's level and zoneThese talk to the running server (--url, with --token for an operator API token or --cookie), like browserhive admin tokens --url.
What leaves your machine
Section titled “What leaves your machine”A channel sends notifications to a service you chose, so each one has a content level:
| Level | What it sends |
|---|---|
counts |
The kind of event, a count and the session name. Nothing typed by an agent, no page addresses. |
titles (default) |
Adds the title, the summary and the facts (session, tool, error code, page address without query string). |
full |
Adds the agent’s own words (the attention reason), longer details, and allows screenshots. |
At every level, BrowserHive first removes registered secrets and credential-shaped text and strips query strings and fragments from URLs (security). Channel tokens never reach a log line, the database or the delivery log. Act-button tokens exist only in the chat message; the database keeps a hash, and no log, delivery row or preview shows one.
Retention
Section titled “Retention”Read or dismissed notifications are kept for 30 days, others for 90. Reports (digests and anomaly alerts) are kept for 90 days whether or not they were read or dismissed, so the Reports tab keeps its history. Delivery history is kept for 30 days. The act-button audit is kept for 90 days, like the other audit records; used or expired button tokens are removed a day after they expire. Configured channels are never pruned; browserhive purge lists them with everything else in the database.