Theme
Events
Your host page can subscribe to events inside the widget — for example to show your own notification when a message arrives, or to update your own interface when an agent comes online.
Subscribing
There are two ways; both attach to the same event bus.
1. In the setup object (Configuration):
js
initWebfon({
providerId: 'PROVIDER_ID',
onMessage: (m) => console.log('new message', m),
onAgentAvailable: (a) => console.log('agent online', a),
});2. At runtime with onEvent — returns a function that unsubscribes:
js
// CDN
const off = window.Webfon.onEvent('message', (m) => console.log('new message', m));
off(); // unsubscribe
// npm
import { onEvent, offEvent } from '@webfon/client';
const off = onEvent('agentAvailable', (a) => console.log(a));If your handler throws, the error is caught — neither the widget flow nor the other handlers are affected. An unknown event name is silently ignored: onEvent returns a no-op unsubscribe function.
Event list
| Event | Setup key | Payload | When |
|---|---|---|---|
message | onMessage | message object (below) | A chat message arrives from the socket. |
agentAvailable | onAgentAvailable | agent object | An agent transitions to an available state (online/away). |
message payload
js
{
id, content, // uuid · text (may be '': an attachment-only message)
from_me, // bool — always false in this event (see below)
send_at, // ISO date
sender_type, // 'agent' | 'system' | 'ai_agent'
agent_id, // sending agent (not the assigned one) — null if absent
file, file_type, // bool · 'photo'|'video'|'audio'|'file'|null (first attachment)
url, file_name, file_size,// shortcuts for the first attachment — null if absent
media, // all attachments: [{id, url, mime, size, file_name}] ([] if absent)
reply_to, // id of the replied-to message — null if absent
is_pinned, // bool
reactions, // { "<uid>": [{type:'emoji', emoji}] } — null if absent
}Attachment URLs are temporary
url and media[].url are presigned addresses in private storage and are valid for about one hour. Do not store them permanently.
agentAvailable payload
js
{ user_id, first_name, last_name, avatar, title, status } // status: 'online' | 'away'It fires only on the transition — not for the full agent roster received at connection time. It also requires the agent roster to have loaded (the roster is fetched when the chat/call page first opens); state changes that arrive before the roster cannot be matched and are not emitted.
What does not trigger message
- Messages you send — the widget drops your own messages (
from_me) before emitting, somessagealways comes from the other side. Your own confirmation is a separatemessage_sentframe and emits no event. - History — the message history loaded when chat opens or reconnects emits no event; only live incoming messages do. A message that arrives twice still emits a single event.
- Edit / reaction / pin updates.
Events only flow while the widget is connected
Not suitable for server-side notifications
The widget connects lazily — not the moment the page loads. It connects:
- when the panel opens,
- when the chat/call page first opens,
- while another tab has a live conversation (then it connects in the background even while the widget is closed, so messages and notifications keep flowing).
If a visitor never opened the widget and has no open conversation, nothing is connected and message never fires. If you need to catch every message without missing any, use a server-side integration.
Subscription lifetime
Subscriptions are not tied to the widget instance: they stay registered after destroy(), so if the widget is destroyed and initialized again, the old handlers keep running. The same function reference is not registered twice, but if you pass a new inline function on every init (onMessage: (m) => …) the handlers accumulate.
In applications with a mount/unmount cycle, instead of passing the callback in the setup object, call the unsubscribe function returned by onEvent during cleanup:
jsx
useEffect(() => {
const widget = initWebfon({ providerId: 'PROVIDER_ID' });
const off = onEvent('message', (m) => console.log(m));
return () => { off(); widget?.destroy(); };
}, []);