Extensions
An extension is your own web page running on a creator's channel: a panel under the stream, an overlay across the whole video, or a component on part of it. You build it with HTML, CSS and JavaScript, and Xive loads it in a sandboxed frame on its own address, never on thexive.com, so it can't read the viewer's Xive session or the page around it.
If you only need to show text, stats and links, an app panel is simpler: you send data and Xive draws it, with no review. An extension is for anything interactive.
Where it can go
| Spot | Size | Per channel |
|---|---|---|
panel | 318px wide; you choose the height, 100–500px. | Up to 3 |
video_overlay | The whole video, transparent where you draw nothing. | 1 |
video_component | A box on the video, as a percentage of its width (10–50%) and height (10–100%). Viewers can hide it. | Up to 2 |
A channel has either an overlay or components on the video, never both. One extension can support any mix of spots; the creator picks where to put it.
Versions and review
Every change ships as a version, and every version goes through the same steps:
| State | Files come from | Who sees it |
|---|---|---|
| Local Test | Your testing base URI | Your team and your test accounts |
| Hosted Test | Xive's extension host | Your team and your test accounts |
| In review | Xive's extension host | Your team, your test accounts and Xive's reviewers |
| Changes requested | — | Your team and your test accounts. Xive's note says what to fix. |
| Approved | Xive's extension host | Your team and your test accounts, until you release it |
| Released | Xive's extension host | Everyone, on every channel that has your extension |
- Only Local Test can be edited. From Hosted Test on, the version is frozen, so what Xive reviewed is exactly what runs. To change it, move it back to Local Test, or create a new version.
- One version can be in review at a time, and one is released at a time. Releasing a version retires the previous one, and every channel moves to the new one straight away.
- Version numbers look like
1.0.0and each new one must be higher than every existing one. A new version starts as a copy of the newest. - Submitting and releasing are for the app's owners and admins. Anyone on the team can edit, test and add test accounts. See roles.
- Xive can take a released version down if it breaks the developer policies. You'll get the reason as a note.
Version settings
| Setting | Notes |
|---|---|
| Pages | One .html page per spot, a path inside your files like panel.html. Optionally a configure page creators set it up on, and a live configure page for their dashboard. |
| Testing base URI | Where Local Test loads your pages from: https://, or http://localhost while you develop. Never a Xive address. |
| Servers it talks to | Up to 20 https:// or wss:// origins your pages may fetch from or open sockets to. Xive's API is always allowed. Anything else is blocked. |
| Configuration required | When on, the extension stays off a channel until the creator has saved its configuration. |
| Identity | When on, viewers can choose to share their Xive identity with your extension. Without it you only get an anonymous id per viewer (see the viewer token). |
| Notes for reviewers | How to test it: a walkthrough, test logins, whether it needs a live stream or your server running. A version that can't be tested is sent back. |
Testing on your channel
In the Console, Add to my channel puts the version you're looking at on your own channel. You and your test accounts see test versions wherever the extension is installed; everyone else sees the released version, or nothing. Add test accounts by Xive username, up to 100. Everyone in your organization can already see test versions.
The viewer token
Your page never gets the viewer's Xive session. Instead Xive hands it a token for the person watching: a JWT signed with HS256 using your extension secret (in the Console), base64-decoded. Send it to your server with each request and verify it there. The claims use the same names as Twitch's extension tokens, so the same verification code works.
{
"exp": 1791234567,
"iat": 1791230967,
"opaque_user_id": "UFxWhP9pzx0SDMFSKI83kmT",
"user_id": "3f0c7a52-0d1e-4f57-9a43-2d8c0b1e6f11",
"channel_id": "c0901ae8-6771-4e9a-b40c-b6e72d3ca397",
"role": "viewer",
"is_unlinked": false,
"pubsub_perms": { "listen": ["broadcast", "global", "whisper-UFxWhP9pzx0SDMFSKI83kmT"], "send": [] }
}
| Claim | Notes |
|---|---|
opaque_user_id | Starts with U for a signed-in viewer: the same every time for that viewer in your extension, and different in every other extension. Starts with A for a signed-out viewer and lasts only as long as their visit. |
user_id | The viewer's Xive id, only when your version asks for identity and they chose to share it. |
is_unlinked | true when the viewer is signed in but hasn't shared their identity with you. |
role | broadcaster, moderator or viewer. Trust it for permissions, such as letting only the broadcaster change settings. |
channel_id | The channel the page is on. |
exp | Tokens last an hour; Xive gives your page a fresh one before then. |
// Node.js — your server
import jwt from 'jsonwebtoken';
const secret = Buffer.from(process.env.XIVE_EXTENSION_SECRET, 'base64');
export function viewer(req) {
const token = req.headers.authorization?.replace(/^Bearer /, '');
return jwt.verify(token, secret, { algorithms: ['HS256'] }); // throws if forged or expired
}
Rotating the secret: a new secret signs every token from then on, and the old one keeps verifying for 24 hours, so verify with both while your servers switch over.
The helper
Load Xive's helper in every page of your extension. It's how your page gets its token, its configuration and live messages. Your page talks only to the Xive page around it, never to the viewer's session.
The helper is also available as window.Twitch.ext, with the same functions, so a page written for Twitch's helper runs once you swap the script tag. What it supports: onAuthorized, onContext, onVisibilityChanged, onError, listen / unlisten / send, configuration (broadcaster, developer, global, onChanged, set), viewer (opaqueId, id, role, isLinked, subscriptionStatus), actions.requestIdShare() and actions.followChannel() (both show the viewer a prompt Xive draws; actions.onFollow(cb) tells you the answer), and credits (also as bits).
Follows, subscriptions and chat
- Follow.
Xive.ext.actions.followChannel()asks the viewer, in Xive's own dialog, to follow the channel they're watching.actions.onFollow(function (didFollow, channelName) {…})tells you what they chose. It only ever offers the current channel. - Subscription status. Turn on Subscription status on a version (it needs identity too). For viewers who share their identity with you,
Xive.ext.viewer.subscriptionStatusis{tier: "1000"}while they subscribe to the channel, andnullotherwise. Xive has one subscription level, reported as tier1000.features.isSubscriptionStatusAvailablesays whether you can see it for this viewer. - Chat. Turn on Post in chat on a version, and your server can post in the chat of any channel your extension is active on, as your extension:
POST /api/extensions/chatwith{channel_id, text}(up to 280 characters), using a token with roleexternal. Up to 12 messages a minute per channel. Messages go through Xive's chat filter and show with your extension's name and a BOT tag.
Each of these is part of the version, so Xive reviews it.
Configuration
Each extension has three configuration segments, each a string of up to 5 KB (usually JSON):
- broadcaster: per channel. Your configure page sets it with
Xive.ext.configuration.set('broadcaster', version, content), with the creator's broadcaster token behind it. - developer: per channel, set only by your server.
- global: one for every channel, set only by your server.
Pages get all three when they load and again whenever one changes. If your version has configuration required turned on, the extension stays off a channel until its broadcaster segment exists.
Live messages
Send a message to everyone watching a channel (broadcast), to every channel your extension is on (global), or to one viewer (whisper-<opaque id>). Each message is a string of up to 5 KB. The broadcaster's page can send to broadcast; your server can send to any target. Up to 100 messages a minute per channel. Signed-in viewers get messages at once. Signed-out viewers get them within about 10 seconds, whispers included (to their A id).
Credits
Viewers buy credits in Xive and can spend them in your extension. You list what you sell as items on the extension — every version sells the same ones — each with a sku, the name viewers see, a price in credits (1–100,000) and whether a purchase is announced to every viewer's copy of your extension.
- In development: a new item. Change it freely; only your team, test accounts and Xive reviewers see it, and buying it is free.
- Submit for review: Xive approves it before it sells for real on your released version.
- Live: a change to its name, price or announcement is a proposal Xive reviews. The live values keep selling until it's approved; if it isn't, you get Xive's note.
- Retire: stops selling it at once, no review needed.
Xive.ext.credits.getProducts().then(function (products) { /* [{sku, displayName, cost: {amount, type: 'credits'}}] */ });
button.onclick = function () { Xive.ext.credits.useCredits('vote.boost'); };
Xive.ext.credits.onTransactionComplete(function (tx) {
// tx.transactionReceipt: a JWT signed with your extension secret — send it to your server and verify it there
});
Xive.ext.credits.onTransactionCancelled(function () { /* the viewer said no */ });
- Xive asks, not you.
useCreditsopens Xive's own confirmation with the item, its price and the viewer's balance. Nothing is spent unless the viewer confirms there. If they're short, it sends them to top up. - Trust the receipt, not the page. The receipt's
datahastransactionId,time,userId(the opaque id),channelIdandproduct(sku,displayName,cost,inDevelopment). Grant the item only after your server verifies it, and usetransactionIdto make sure you grant it once. Your server can also list purchases withGET /api/extensions/transactions?since=. - Who gets what. The streamer gets the same share they'd get from a gift, and you get 10% of the purchase out of Xive's share. Your share is paid to your app's owner (for an organization, its owner) through Xive's creator payouts.
- Testing is free. In a version that isn't released, purchases cost nothing and receipts say
inDevelopment: true. - Announced items. When an announced item is bought, every other viewer's copy of your extension gets
onTransactionCompletetoo, withinitiator: "other". - From your server.
GET /api/extensions/itemslists them;PUT /api/extensions/itemswith{sku, name, credits, broadcast?, submit?}creates or changes one under the same rules (a live item's change is a proposal;submit: trueputs a new one up for review). - The names on Twitch's helper work too:
Twitch.ext.bits.useBits(sku),getProducts(),onTransactionComplete. The cost type iscredits.
Calling Xive from your server
Your server signs its own token with your extension secret: HS256, role set to external, a short exp, and channel_id when you're acting on one channel. Send it as a bearer token with your client id in a Client-Id header. A viewer token from onAuthorized works too, but only for that viewer's channel and role.
// Node.js — set the developer segment, then tell everyone watching
const token = jwt.sign({ role: 'external', channel_id: CHANNEL_ID, exp: Math.floor(Date.now() / 1000) + 60 },
Buffer.from(process.env.XIVE_EXTENSION_SECRET, 'base64'), { algorithm: 'HS256' });
const headers = { Authorization: `Bearer ${token}`, 'Client-Id': CLIENT_ID, 'Content-Type': 'application/json' };
await fetch('https://api.thexive.com/api/extensions/configuration', { method: 'PUT', headers,
body: JSON.stringify({ segment: 'developer', content: JSON.stringify({ round: 3 }), version: '1' }) });
await fetch('https://api.thexive.com/api/extensions/pubsub', { method: 'POST', headers,
body: JSON.stringify({ target: 'broadcast', message: JSON.stringify({ question: 'Who won?' }) }) });
| Call | Notes |
|---|---|
GET /api/extensions/configuration?channel_id= | All three segments for the channel. |
PUT /api/extensions/configuration | {segment, content, version?, channel_id?}. channel_id isn't used for global. |
POST /api/extensions/chat | {channel_id, text}. Needs Post in chat on the version the channel runs. |
POST /api/extensions/pubsub | {target, message, content_type?, channel_id?}. target can be a list of up to 3. Too many answers 429 with Retry-After. |
Analytics
The Console's Analytics card shows, for the last 30 days: how many times your extension was drawn on a channel page, installs and removals, how often creators turned it on or off, purchases and credits, and your share. It also shows how many channels have it installed and active right now. Numbers are counted as they happen and can lag by a minute; test purchases aren't counted.
What your pages may do
- Run scripts from your own files. Inline scripts, and scripts or frames from other addresses, are blocked.
- Talk to Xive's API and the servers you listed. Nothing else.
- Open links in a new tab when the viewer clicks. Your page can't navigate the channel page, open dialogs, or download files without a click.
- Never ask a viewer for their Xive password or payment details. Anything to do with money goes through Xive's own prompts.