1. Home
  2. Docs
  3. Events

Widget events

Subscribe with Pong('on', name, handler). Handlers run in your page, so there is no postMessage parsing and no origin checks to write.

function onMessage(m) {
  if (m.type === 'agent') Pong('openChat', true);
}
Pong('on', 'message', onMessage);
Pong('off', 'message', onMessage); // one handler
Pong('off', 'message');            // all handlers of this event

Event list

EventPayloadWhen
ready{ clientId, verified, unread, lastAgentMessageAt, banned }The widget connected and knows the visitor.
message{ id, type, channel, text, attachments, agentId, createdAt }A message was sent or received. type is visitor, agent, ai, system or bot (contact form bot).
unread{ count, lastAgentMessageAt }The number of agent messages the visitor has not seen changed.
open / close{ reason }The chat window opened or closed; reason is user, api or trigger.
trigger{ id, action }A trigger fired on this page. action is message, open, hide or group; the server has already done its part (the message, the group).
identity{ clientId, verified, merged }After identify. merged is true when a signed identity joined this browser to an existing client.
banned{ banned }An agent blocked or unblocked the visitor.
call{ state, video }An agent call: state is incoming (ringing), active (answered) or ended (hung up, declined or cancelled); video is false for a voice call.
contact{}The visitor filled in the contact form. The details go to the client card, not to the page.
restart{}After Pong('restart').
Unread counts and the last agent message time are kept on the server per client, so they are right across tabs and devices. You don't need any localStorage bookkeeping.

Count triggers in your analytics

Pong('on', 'trigger', (t) => {
  window.plausible && plausible('Chat trigger', { props: { id: t.id, action: t.action } });
});

State classes on <html>

The widget keeps classes on your page's <html> element in step with its state, so your own CSS can react without any JavaScript. A class is added or removed only when that state changes; nothing else on your page is touched.

ClassPresent while
pong-chat-loadedThe widget connected and knows the visitor (after ready).
pong-chat-openedThe chat window is open.
pong-chat-minimizedThe widget is shown as its launcher button, the chat window closed.
pong-chat-hiddenThe widget is hidden (Pong('hide'), startHidden) or removed with Pong('kill').
pong-chat-new-messageThe visitor has agent messages they have not seen.
pong-chat-bannedAn agent blocked the visitor.
pong-chat-incoming-callAn agent is calling and the visitor has not answered yet.
pong-chat-video-callA video call is in progress.
pong-chat-audio-callA voice call is in progress.
pong-chat-screen-sharingThe visitor is sharing their screen in a call.
pong-chat-mobileThe widget uses its phone layout (full-screen chat).
/* Hide your own support button for blocked visitors */
.pong-chat-banned #support { display: none; }

/* A dot on your menu while there is an unread reply */
.pong-chat-new-message #support::after { content: '●'; color: #e5484d; }

/* Hide a background video while the visitor is on a call */
.pong-chat-video-call .hero video,
.pong-chat-audio-call .hero video { visibility: hidden; }

Recipes

Pong('on', 'unread', (u) => {
  document.querySelector('#support').classList.toggle('has-unread', u.count > 0);
});

Open the chat when an agent writes

Pong('on', 'message', (m) => {
  if (m.type === 'agent') { Pong('show'); Pong('expandWidget'); }
});

Events in Google Tag Manager

When the page has a dataLayer (GTM, or gtag.js), the widget pushes its events there as { event: 'pong_…' }. In GTM make a trigger of type Custom Event with that name and send it to GA4, Google Ads or any other tag. No message texts or contact details are pushed.

EventExtra keysWhen
pong_chat_opened / pong_chat_closedpong_reason: user, api or triggerThe chat window opened or closed.
pong_chat_startedThe visitor sent their first message (once per browser): the usual "lead" conversion.
pong_message_sentEvery visitor message.
pong_reply_receivedpong_from: agent or aiThe first answer from an agent or the AI after the visitor wrote (trigger greetings don't count).
pong_contact_sentThe visitor filled in the contact form.
pong_triggerpong_trigger: the trigger's name; pong_actionA trigger fired.
pong_call_answeredpong_videoThe visitor answered an agent's call.

Use the extra keys in GTM as Data Layer Variables. The pushes are on by default; turn them off or set a renamed data layer in Settings → Chat widget → Analytics. A page can override that with PongSetup = { dataLayer: 'myLayer' }, or dataLayer: false for none.

Send chat events to your analytics

Without GTM, or for any other tool, pass a function:

PongSetup = {
  onAnalyticEvent(name) {
    // 'Chat opened', 'Chat closed', 'Message sent', 'Chat initiated by agent'
    window.gtag && gtag('event', name, { event_category: 'chat' });
  },
};

← JavaScript APIPongSetup options →