PubSub Guide
Introduction
PubSub enables you to subscribe to a topic, for updates (e.g., when a user cheers in a channel).
The Twitch PubSub system allows back-end services to broadcast realtime messages to clients. Example applications include:
- An instant messaging service sending instant messages between friends.
- A back-end video system pushing real-time viewer count updates to video players.
- A presence system broadcasting users’ online status to all their friends.
These example applications share a common pattern: on application load, the application fetches a complete snapshot of its state and uses a PubSub connection to receive updates to this state. The updates act as “diffs” to the initial state.
Clients establish a WebSocket connection to our server, listen on topics they care about, and get messages on those topics in real time. Each command or message sent between the client and server is a JSON string encapsulated in one WebSocket frame.
(WebSocket is a communications protocol, providing full-duplex communication channels over a single TCP connection. For more information, see the WebSocket Wikipedia entry.)
The JSON messages differ, depending on the type of message or command, but typically they have this form:
{
"type": "" ,
"data": ""
}
Code samples are available here.
Terminology
| Term | Definition |
|---|---|
| Client | An end-user session of the Twitch application or a third-party application’s integration point. |
| Command | An action that a client issues to the server, which modifies the state of the client connection. |
| Message | A piece of data which back-end services broadcast to interested clients via PubSub. PubSub never inspects or mutates messages. |
| Server | A Twitch machine that clients connect to for PubSub service. |
| Topic | Aka event. A logical partition of messages that clients may subscribe to, to get messages. |
Connection Management
Clients establish a secure WebSocket connection to:
wss://pubsub-edge.twitch.tv
To keep the server from closing the connection, clients must send a PING command at least once every 5 minutes. If a client does not receive a PONG message within 10 seconds of issuing a PING command, it should reconnect to the server. See details in Handling Connection Failures.
Clients must LISTEN on at least one topic within 15 seconds of establishing the connection, or they will be disconnected by the server.
Clients may receive a RECONNECT message at any time. This indicates that the server is about to restart (typically for maintenance) and will disconnect the client within 30 seconds. During this time, we recommend that clients reconnect to the server; otherwise, the client will be forcibly disconnected.
Example: PING Connection Message
// Sent from client to server
{
"type": "PING"
}
Example: PONG Connection Message
// Sent from server to client in response to a PING
{
"type": "PONG"
}
Example: RECONNECT Connection Message
// Sent from server to client
{
"type": "RECONNECT"
}
Handling Connection Failures
When a client encounters a situation where it must reconnect to the server, it should first establish a new successful WebSocket connection and then issue a LISTEN command that contains the set of topics that the application expects to receive messages on.
If a client fails to connect to the server or is disconnected from the server, it should reconnect to the server using an exponential backoff. Our official client implementation initially waits one second (plus a small random jitter: see the next paragraph) to retry a failed connection and doubles the backoff period on subsequent failures, up to a maximum backoff threshold of 2 minutes. This prevents some “stampeding herd” situations where many clients simultaneously connect to the server.
If a client uses timers to issue PING commands, it should add a small random jitter to the timer. This prevents some situations where many clients issue PING commands simultaneously.
After a client reconnects to the server, some applications should fetch fresh state, to compensate for any missed messages while the connection was broken. For instance, our Chat application fetches the Bits leaderboard on reconnect (as it does on initial page load), in case the leaderboard received an update while the connection was down.
API Limits
- Clients can listen on up to 50 topics per connection. Trying to listen on more topics will result in an error message.
- We recommend that a single client IP address establishes no more than 10 simultaneous connections.
The two limits above are likely to be relaxed for approved third-party applications, as we start to better understand third-party requirements.
- Malicious or careless applications that result in abnormally high server load may be blacklisted from establishing connections.
- If clients are slow to read messages from their connection and the server is simultaneous buffering more than 30 messages to an individual client, the client will be disconnected.
Topics
Once a client establishes a connection, it can LISTEN on topics it cares about. Clients can LISTEN on many topics at once and add new topics at any time by issuing new LISTEN commands.
Once a client no longer cares about a particular set of topics, it should issue UNLISTEN commands to stop receiving messages on those topics.
Authentication
All topics require an OAuth token, but only some topics have a specific required scope (noted in the table below).
Available Topics
All topics require an OAuth token, but only some topics have a specific required scope (noted in the table).
| Feature | Topic and Example | Required Scope | You are notified when … |
|---|---|---|---|
| Bits | channel-bits-events-v1.Example: channel-bits-events-v1.44322889 |
Any scope | Anyone cheers in a specified channel. |
| Bits | channel-bits-events-v2Example: "topic": "channel-bits-events-v2.46024993" |
bits:read |
Anyone cheers in a specified channel. |
| Bits Badge Notification | channel-bits-badge-unlocks.Example: channel-bits-badge-unlocks.44322889 |
Any scope | Message sent when a user earns a new Bits badge in a particular channel, and chooses to share the notification with chat. |
| Channel Subscriptions | channel-subscribe-events-v1.Example: channel-subscribe-events-v1.44322889 |
channel_subscriptions |
Anyone subscribes (first month), resubscribes (subsequent months), or gifts a subscription to a channel. Subgift subscription messages contain recipient information. |
| Commerce | channel-commerce-events-v1.Example: channel-commerce-events-v1.44322889 |
Any scope | Anyone makes a purchase on a channel. |
| Whispers | whispers.Example: whispers.44322889 |
whispers:read |
Anyone whispers the specified user. |
Where:
is the_idfield returned by the Twitch API v5 Get Channel endpoint.is the_idfield returned by the Twitch API v5 Get User endpoint.
Note: channel-bitsevents is deprecated. For Bits events, use channel-bits-events-v1 instead.
Requests
This is an example request for Bits events. It listens to Bits events on channel 44322889. The authorization scope is specified when you generate the OAuth token using our authorization flow; see the Apps & Authentication Guide.
// Request from client to server
{
"type": "LISTEN",
"nonce": "44h1k13746815ab1r2",
"data": {
"topics": ["channel-bits-events-v1.44322889"],
"auth_token": "cfabdegwdoklmawdzdo98xt2fo512y"
}
}
| Request Parameter | Type | Description |
|---|---|---|
type | string | Valid values: LISTEN, UNLISTEN. |
nonce | string | (Optional) Random string to identify the response associated with this request. |
data | JSON | Wraps the topics and auth_token fields. |
topics | array of strings | List of topics to listen on. Valid values are any of the topics listed in Available Topics. |
auth_token | string | OAuth token required to listen on some topics. The token is linked to either the specified (for Bits events) or the specified (for whispers events). |
Responses
Here is a sample response:
// Response from server to client
{
"type": "RESPONSE",
"nonce": "44h1k13746815ab1r2",
"error": ""
}
| Field | Type | Description |
|---|---|---|
type |
string | Valid value: RESPONSE. |
nonce |
string | The nonce that was passed in the request, if one was provided there. |
error |
string | The error message associated with the request, or an empty string if there is no error. For Bits and whispers events requests, error responses can be: ERR_BADMESSAGE, ERR_BADAUTH, ERR_SERVER, ERR_BADTOPIC. |
Receiving Messages
When a message for your subscription is published, you will receive a message containing the applicable data.
Message Parameters: All Messages
| Parameter | Type | Description |
|---|---|---|
type | string | Valid value: MESSAGE |
data | JSON | Wraps the topics and message fields. |
topic | string | The topic that the message pertains to. |
message | string | The body of the message. Depending on the type of message, the message body contains different fields; see below. |
Example: Bits Event v2 Message
{
"type": "MESSAGE",
"data": {
"topic": "channel-bits-events-v2.46024993",
"message": "{\"data\":{\"user_name\":\"jwp\",\"channel_name\":\"bontakun\",\"user_id\":\"95546976\",\"channel_id\":\"46024993\",\"time\":\"2017-02-09T13:23:58.168Z\",\"chat_message\":\"cheer10000 New badge hype!\",\"bits_used\":10000,\"total_bits_used\":25000,\"context\":\"cheer\",\"badge_entitlement\":{\"new_version\":25000,\"previous_version\":10000}},\"version\":\"1.0\",\"message_type\":\"bits_event\",\"message_id\":\"8145728a4-35f0-4cf7-9dc0-f2ef24de1eb6\",\"is_anonymous\":true}"
}
}
| Field | Type | Description |
badge_entitlement(optional) |
object | Information about a user’s new badge level, if the cheer was not anonymous and the user reached a new badge level with this cheer. Otherwise, null. |
bits_used |
integer | Number of bits used. |
channel_id |
string | ID of the channel in which Bits were used. |
chat_message |
string | Chat message sent with the cheer. |
context |
string | Event type associated with this use of Bits. |
is_anonymous |
Boolean | Whether or not the event was anonymous. |
message_id |
string | Message ID. |
message_type |
string | The type of object contained in the data field. |
time |
string | Time when the Bits were used. RFC 3339 format. |
total_bits_used |
integer | All time total number of Bits used in the channel by a specified user. |
user_id (optional) |
string | User ID of the person who used the Bits - if the cheer was not anonymous. Null if anonymous. |
user_name(optional) |
string | Login name of the person who used the Bits - if the cheer was not anonymous. Null if anonymous |
version |
string | Message version |
Example: Bits Event v1 Message
{
"type": "MESSAGE",
"data": {
"topic": "channel-bits-events-v1.44322889",
"message": "{\"data\":{\"user_name\":\"dallasnchains\",\"channel_name\":\"dallas\",\"user_id\":\"129454141\",\"channel_id\":\"44322889\",\"time\":\"2017-02-09T13:23:58.168Z\",\"chat_message\":\"cheer10000 New badge hype!\",\"bits_used\":10000,\"total_bits_used\":25000,\"context\":\"cheer\",\"badge_entitlement\":{\"new_version\":25000,\"previous_version\":10000}},\"version\":\"1.0\",\"message_type\":\"bits_event\",\"message_id\":\"8145728a4-35f0-4cf7-9dc0-f2ef24de1eb6\"}"
}
}
| Field | Type | Description |
|---|---|---|
badge_entitlement |
object | Information about the user’s new badge level, if the user reached a new badge level with this cheer; otherwise. null. |
bits_used |
integer | Number of Bits used. |
channel_id |
string | User ID of the channel on which Bits were used. |
channel_name |
string | Name of the channel on which Bits were used. |
chat_message |
string | Chat message sent with the cheer. |
context |
string | Event type associated with this use of Bits (for example, cheer). |
message_id |
string | Message ID |
message_type |
string | Message type (that is, the type of object contained in the data field) |
time |
string | Time when the Bits were used. RFC 3339 format. |
total_bits_used |
integer | All-time total number of Bits used on this channel by the specified user. |
user_id |
string | User ID of the person who used the Bits. |
user_name |
string | Login name of the person who used the Bits. |
version |
string | Message version |
Example: Bits Badge Notification Message
{
"type":"MESSAGE","data":{"topic":"channel-bits-badge-unlocks.401394874","message":"
{
\"user_id\":\"232889822\",\"user_name\":\"willowolf\",\"channel_id\":\"401394874\",\"channel_name\":\"fun_test12345\",\"badge_tier\":1000,\"chat_message\":\"this should be received by the public pubsub listener\",\"time\":\"2020-12-06T00:01:43.71253159Z\"}"
}
}
| Field | Type | Description |
user_id |
string | ID of user who earned the new Bits badge |
user_name |
string | Login of user who earned the new Bits badge |
channel_id |
string | ID of channel where user earned the new Bits badge |
channel_name |
string | Login of channel where user earned the new Bits badge |
badge_tier |
integer | Value of Bits badge tier that was earned (1000, 10000, etc.) |
chat_message |
string | [Optional] Custom message included with share |
time |
string | Time when the new Bits badge was earned. RFC 3339 format. |
Example: Channel Subscriptions Event Message
The data field is a JSON object that contains topic and message fields. Typically, message is a JSON object that has been escaped (see “Example: Bits Event Message” above) and cast into a string.
Note: The months field is deprecated. We now have fields for cumulative-months and streak-months.
Following is an example of a sub/resub message:
{
"type": "MESSAGE",
"data": {
"topic": "channel-subscribe-events-v1.44322889",
"message": {
"user_name": "dallas",
"display_name": "dallas",
"channel_name": "twitch",
"user_id": "44322889",
"channel_id": "12826",
"time": "2015-12-19T16:39:57-08:00",
"sub_plan": "Prime"/"1000"/"2000"/"3000",
"sub_plan_name": "Channel Subscription (mr_woodchuck)",
"cumulative-months": 9;
"streak-months": 3,
"context": "sub"/"resub",
"sub_message": {
"message": "A Twitch baby is born! KappaHD",
"emotes": [
{
"start": 23,
"end": 7,
"id": 2867
}]
}
}
}
}
And here is an example of a subgift message, which contains recipient information.
{
"type": "MESSAGE",
"data": {
"topic": "channel-subscribe-events-v1.44322889",
"message": {
"user_name": "dallas",
"display_name": "dallas",
"channel_name": "twitch",
"user_id": "44322889",
"channel_id": "12826",
"time": "2015-12-19T16:39:57-08:00",
"sub_plan": "1000"/"2000"/"3000",
"sub_plan_name": "Channel Subscription (mr_woodchuck)",
"months": 9,
"context": "subgift",
"sub_message": {
"message": "",
"emotes": null },
"recipient_id": "13405587",
"recipient_user_name": "tww2",
"recipient_display_name": "TWW2",
}
}
}
}
Here is an example of an anonsubgift message. This is like subgift but contains no sender information, since the gifter is anonymous.
{
"type": "MESSAGE",
"data": {
"topic": "channel-subscribe-events-v1.44322889",
"message": {
"channel_name": "twitch",
"channel_id": "12826",
"time": "2015-12-19T16:39:57-08:00",
"sub_plan": "1000"/"2000"/"3000",
"sub_plan_name": "Channel Subscription (mr_woodchuck)",
"months": 9,
"context": "anonsubgift",
"sub_message": {
"message": "",
"emotes": null },
"recipient_id": "13405587",
"recipient_user_name": "tww2",
"recipient_display_name": "TWW2",
}
}
}
}
Example: Commerce Event Message
The data field is a JSON object that contains topic and message fields. Typically, message is a JSON object that has been escaped (see “Example: Bits Event Message” above) and cast into a string.
{
"type": "MESSAGE",
"data": {
"topic": "channel-commerce-events-v1.44322889",
"message": {
"user_name": "dallas",
"display_name": "dallas",
"channel_name": "twitch",
"user_id": "44322889",
"channel_id": "12826",
"time": "2015-12-19T16:39:57-08:00",
"item_image_url": "https://...",
"item_description": "This is a friendly description!",
"supports_channel": true/false,
"purchase_message": {
"message": "A Twitch game is born! Kappa",
"emotes": [
{
"start": 23,
"end": 7,
"id": 2867
}]
}
}
}
}
Example: Whispers Event Message
The data field is a JSON object that contains topic and message fields. Typically, message is a JSON object that has been escaped (see “Example: Bits Event Message” above) and cast into a string.
{
"type":"MESSAGE",
"data":{
"topic":"whispers.44322889",
"message":{
"type":"whisper_received",
"data":{
"id":41
},
"thread_id":"129454141_44322889",
"body":"hello",
"sent_ts":1479160009,
"from_id":39141793,
"tags":{
"login":"dallas",
"display_name":"dallas",
"color":"#8A2BE2",
"emotes":[
],
"badges":[
{
"id":"staff",
"version":"1"
}
]
},
"recipient":{
"id":129454141,
"username":"dallasnchains",
"display_name":"dallasnchains",
"color":"",
"badges":[]
},
"nonce":"6GVBTfBXNj7d71BULYKjpiKapegDI1"
},
"data_object":{
"id":41,
"thread_id":"129454141_44322889",
"body":"hello",
"sent_ts":1479160009,
"from_id":44322889,
"tags":{
"login":"dallas",
"display_name":"dallas",
"color":"#8A2BE2",
"emotes":[],
"badges":[
{
"id":"staff",
"version":"1"
}
]
},
"recipient":{
"id":129454141,
"username":"dallasnchains",
"display_name":"dallasnchains",
"color":"",
"badges":[]
},
"nonce":"6GVBTfBXNj7d71BULYKjpiKapegDI1"
}
}
}
The fields in this message are similar to IRC fields. See the Chatbots and IRC documentation.