Appearance
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:
| Field | Type | Description |
|---|---|---|
context | TV1ExtContext | Updated 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:
| Field | Type | Description |
|---|---|---|
viewers | TV1ExtViewers | Updated 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:
| Field | Type | Description |
|---|---|---|
isAnonymous | boolean | Whether the spend was made anonymously. |
paymentData | TV1PaymentData | Payment data for validating whisper calls. |
tokensSpendData | Record<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:
| Field | Type | Description |
|---|---|---|
tipMenu | TV1TipMenu | The 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:
| Field | Type | Description |
|---|---|---|
tipData | TV1TipData | Tip 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:
| Field | Type | Description |
|---|---|---|
messageText | string | Text of the message. |
sender | TV1ExtUser | User 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:
| Field | Type | Description |
|---|---|---|
data | TV1SessionUpdate | Session 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);
});