Skip to content

Events

Events are messages pushed to your extension. Subscribe to them via subscribe to react to changes in real time.

ts
ext.subscribe('v1.ext.context.updated', ({ context }) => {
  console.log(context);
});

v1.ext.context.updated

Fired whenever the room context changes — for example, when a user joins, the show status changes, or the model goes online/offline.

Payload:

FieldTypeDescription
contextTV1ExtContextUpdated context object containing model and user.

Example

ts
ext.subscribe('v1.ext.context.updated', ({ context }) => {
  if (context.model) {
    console.log('Model status:', context.model.status);
  }

  if (context.user) {
    console.log('User:', context.user.username);
  }
});

TIP

Call v1.ext.context.get once on startup to get the initial context, then use this event to keep it up to date.

v1.ext.activity.available

Fired when the shared activity slot used by this extension becomes free — meaning another extension that was holding the slot has finished or cancelled its activity, and no other extension is currently blocking it.

Use this together with v1.ext.activity.status to synchronise the initial state, and then react to slot changes in real time.

Payload: null

Example

ts
// Sync initial state on page load.
const { status } = await ext.makeRequest('v1.ext.activity.status', null);
if (status === 'busy') {
  disableActivityButton();
}

ext.subscribe('v1.ext.activity.available', () => {
  enableActivityButton();
});

v1.ext.activity.busy

Fired when the shared activity slot used by this extension becomes occupied by another extension's activity request.

Payload: null

Example

ts
ext.subscribe('v1.ext.activity.busy', () => {
  disableActivityButton();
});

v1.ext.viewers.updated

Fired whenever the viewer counts for the room change — for example, when a user joins or leaves. Emitted only when show type is public, groupShow, or ticketShow.

Payload:

FieldTypeDescription
viewersTV1ExtViewersUpdated viewer counts.

Example

ts
ext.subscribe('v1.ext.viewers.updated', ({ viewers }) => {
  console.log('Total viewers:', viewers.total);
  console.log('Viewers with tokens:', viewers.withTokens);
});

TIP

Call v1.ext.viewers.get once on startup to get the initial counts. If the request returns null, this event is not emitted until the show type becomes public, groupShow, or ticketShow.

v1.ext.whispered

Fired when a broadcast whisper arrives. The platform routes the whisper through the server and delivers it to every loaded iframe of this extension across all clients in the room — including the sender.

Payload: Record<string, unknown>

The payload is the data object passed to v1.ext.whisper by the sender.

Example

ts
ext.subscribe('v1.ext.whispered', (data) => {
  if (data.type === 'GAME_STARTED') {
    startGame(data.round);
  }

  if (data.type === 'UPDATE_UI') {
    updateScore(data.payload.score);
  }
});

TIP

Use a type field in your whisper data to distinguish between different message types. This makes it easy to handle multiple message types in a single subscriber.

See Slots Communication for detailed usage and patterns.

v1.ext.whispered.local

Fired when a local whisper arrives from another iframe of the same extension in the current browser tab. Other users never receive it.

Payload: Record<string, unknown>

The payload is the data object passed to v1.ext.whisper.local by the sender.

Example

ts
ext.subscribe('v1.ext.whispered.local', (data) => {
  console.log('Local message:', data);
});

v1.payment.tokens.spend.succeeded

Fired in every iframe of the extension when a token spend initiated by v1.payment.tokens.spend is confirmed by the user and processed by the server. Use paymentData.paymentToken when calling v1.ext.whisper from a public-show user to authorize the broadcast.

Payload:

FieldTypeDescription
isAnonymousbooleanWhether the spend was made anonymously.
paymentDataTV1PaymentDataPayment data for validating whisper calls.
tokensSpendDataRecord<string, unknown>The metadata object passed in the original v1.payment.tokens.spend request.

Example

ts
ext.subscribe('v1.payment.tokens.spend.succeeded', (data) => {
  console.log('Payment token:', data.paymentData.paymentToken);
  console.log('Metadata:', data.tokensSpendData);
  console.log('Anonymous:', data.isAnonymous);
});

v1.tipMenu.updated

Fired when the tip menu has been updated.

Payload:

FieldTypeDescription
tipMenuTV1TipMenuThe current tip menu data

Example

ts
ext.subscribe('v1.tipMenu.updated', ({ tipMenu }) => {
  if (tipMenu.isEnabled) {
    console.log('Tip menu items:', tipMenu.items.length);
  }
});

v1.tokens.spent

Fired on every tip received in the room — both from public chat (including PRIVATE_TIP messages visible during private/exclusive shows) and via private messages. Fires in every loaded iframe of the extension for both the model and the viewer.

Payload:

FieldTypeDescription
tipDataTV1TipDataTip details including amount, source, and user info.

Example

ts
ext.subscribe('v1.tokens.spent', ({ tipData }) => {
  console.log(`Received ${tipData.amount} tokens from ${tipData.source}`);

  if (tipData.isFromTheCurrentUser) {
    console.log('This tip was sent by the current user');
  }

  if (tipData.user) {
    console.log('Tipped by:', tipData.user.username);
  }
});

v1.model.ext.settings.set.requested

Fired in the settings iframe when the model clicks Save in the settings modal. Your handler must call v1.model.ext.settings.set before the modal can close — passing isError: true keeps the modal open for correction, passing isError: false triggers the server save and closes it.

Payload: null

Example

ts
ext.subscribe('v1.model.ext.settings.set.requested', async () => {
  const settings = { theme: 'neon', rounds: 5 };
  const isError = false;

  await ext.makeRequest('v1.model.ext.settings.set', {
    settings,
    isError,
  });
});

See Extension Settings for the full settings flow.

v1.chat.message.received

Triggered on every new message in chat.

Payload:

FieldTypeDescription
messageTextstringText of the message.
senderTV1ExtUserUser who sent the message.
type'private' | 'public'Whether message was sent in public or private chat.

Example

ts
ext.subscribe('v1.chat.message.received', (data) => {
  console.log(`${data.sender.username}: ${data.messageText}`);
});

v1.session.updated

Fired on session lifecycle changes. data.action describes why the update happened (join or finish).

Payload:

FieldTypeDescription
dataTV1SessionUpdateSession lifecycle update payload.

Example

ts
ext.subscribe('v1.session.updated', ({ data }) => {
  if (data.session?.owner === 'self') {
    console.log('Session active:', data.session.meta.sessionId);
  } else {
    console.log('No active session');
  }
});

v1.session.whispered.model

Fired on the model (broadcaster) client only when a viewer calls v1.session.whisper.user. Viewers do not receive this event.

Use it to validate the proposed action, update authoritative state, then publish the result to the room (for example with v1.ext.whisper) so all clients stay in sync.

Payload: TV1SessionWhisperData

Example

ts
// Subscribe only in extension code running on the broadcaster's client (e.g. background slot).
ext.subscribe('v1.session.whispered.model', (data) => {
  console.log('Viewer proposed session action:', data);
});