Introduction

This guide quickly brings you up to speed on Stream’s Chat API. If you haven’t seen the React & React Native components yet be sure to check those out first:

You mainly need these API docs when you want to customize the frontend components or build your own. The API is flexible and allows you to build any type of chat.

You're currently not logged in. This means that the code examples below will use placeholders and will not work out of the box. Log in so we can add your API key and other information in the documentation snippets elsewhere on this page.

Setup

The Stream Chat API client is available as an npm package.

After installing it, simply import it in your project and you're ready to go:

import { StreamChat } from 'stream-chat';
// or
const StreamChat = require('stream-chat').StreamChat;

Getting started

Chat Client

Let’s get started by installing, initializing the chat client and setting the current user.

// using npm
npm install stream-chat

// using Yarn
yarn add stream-chat
const client = new StreamChat('YOUR_API_KEY');
await client.setUser(
    {
        id: 'jlahey',
        name: 'Jim Lahey',
        image: 'https://i.imgur.com/fR9Jz14.png',
    },
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc',
);

The above snippet is for an in-browser or mobile integration. Server side API calls are a little different, but we will cover them in detail later in the docs.

Channels

Let’s continue by initializing your first Channel. A Channel contains messages, a list of people that are watching the Channel and optionally a list of members (for private conversations). The example below shows how to set up a Channel to support chat for a group conversation:

const channel = client.channel('messaging', 'travel', {
    name: 'Awesome channel about traveling',
});
// fetch the channel state, subscribe to future updates
let state = await channel.watch();

The first two arguments are the Channel Type and the Channel ID (messaging and travel in this case). The Channel type controls the settings we’re using for this Channel. The Channel ID is the unique identifier for this Channel.

There are 5 default types of channels:

  • livestream
  • messaging
  • team
  • gaming
  • commerce
These 5 options give you the most sensible defaults for those use cases. You can also define your own channel types if the defaults don’t work well for you.

The third argument is an object containing custom data. You can add as many custom fields as you want to the object as long as the total size of the object is less than 5KB.

Messages

Now that we have the Channel setup, let's send our first chat message:

const text = 'I’m mowing the air Rand, I’m mowing the air.';
const response = await channel.sendMessage({
    text,
    customfield: '123',
});

Similar to users and Channels, the sendMessage method allows you to add custom fields. When you send a message to a Channel it will automatically broadcast to all the people that are watching this Channel and updates in real time.

Events

This is how you can listen to events on the client side:

channel.on('message.new', event => {
    console.log('received a new message', event.message.text);
    console.log(`Now have ${channel.state.messages.length} stored in local state`);
});

Note how you receive the event and can access the full Channel state via channel.state.

Conclusion

Now that you understand the building blocks of a fully functional chat integration, let’s move on to the next sections of the documentation where we will go into more details on each API endpoint.

Initialization and Users

Initialization for browser/mobile

const chatClient = new StreamChat('YOUR_API_KEY', {
    timeout: 3000,
    httpAgent: new http.Agent({ keepAlive: 3000 }),
    httpsAgent: new http.Agent({ keepAlive: 3000 }),
});

The code above creates a Chat client instance for browser/mobile usage. The first parameter is the API Key and the second an object with additional options.

Extra params

Name Type Description Default Optional
timeout integer Timeout of API calls in milliseconds. 3000
httpAgent object Allows you to specify the keepAlive behavior when making HTTP requests from Node
httpsAgent string Allows you to specify the keepAlive behavior when making HTTPS requests from Node
Note: httpAgent and httpsAgent are ignored when using Stream on the client side.

Setting the user

Once initialized, you must specify the current user with setUser:

await chatClient.setUser(
    {
        id: 'john',
        name: 'John Doe',
        image: 'https://getstream.io/random_svg/?name=John',
    },
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiam9obiJ9.TxsU2JMbx7UXQ166lRabT6h7pAf415tLLKhJEZO_Kbg',
);

Set user params

Name Type Description
user object The user object. Must have id field. It can have as many custom fields as you want, as long as the total size of the object is less than 5KB.
userToken string The user auhentication token. See below for details

Tokens

Tokens are used to authenticate the user. Typically, you send this token from your backend to the client side when an event such as a user login or registration happens within your application.

You can generate tokens server side with the following syntax:

const serverSideClient = new StreamChat('YOUR_API_KEY', 'API_KEY_SECRET');
const token = serverSideClient.createToken('john');

By default user tokens are valid indefinitely. You can set an expiration to tokens by passing it as second parameter. The expiration should contain the number of seconds since the epoch.

// create a token that expires in 1 hour using moment.js
const expirationTimestamp = moment().add('1h').format('X');
const token = serverSideClient.createToken('john', expirationTimestamp);

// the same can be done with plain JS
const token2 = serverSideClient.createToken('john', Math.floor(Date.now()/1000) + (60 * 60));

Development tokens

For development apps it is possible to disable token authentication and use client-side generated tokens. Disabling auth checks is not suitable for a production application and should only done for proof-of-concepts and applications in early development stage. To enable development tokens you need to change your application configs, this can be done using the AppSettings API or using the CLI tool.

await chatClient.setUser(
    {
        id: 'john',
        name: 'John Doe',
        image: 'https://getstream.io/random_svg/?name=John',
    },
   chatClient.devToken('john'),
);

The above code used the setUser call. The setUser call is the most convenient option when your app has authenticated users.
Alternatively you can use setGuestUser if you want to allow users to chat with a guest account or the setAnonymousUser if you want to allow anonymous users to watch the chat.

Guest Users

Guest sessions can be created entirely client side and do not require any server-side authentication.

Guest users have a limited set of permissions. You can read more about how to configure permissions here. You can create a guest user session by using setGuestUser instead of setUser.

await client.setGuestUser({ id: 'tommaso' });
The user object schema is the same as the one described in the Setting the user portion of the docs.
    
    # the guest endpoint returns a user object and a token:
    curl -i -X POST -d "{\"user\":{\"id\":\"tommaso\"}}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: anonymous" \
    "https://chat-us-east-c1.stream-io-api.com/guest?api_key=YOUR_API_KEY"
    
    

Anonymous Users

If a user is not logged in you can call setAnonymous. While you’re anonymous you can’t do much, but for the livestream channel type you’re still allowed to read the chat conversation.

await client.setAnonymousUser();

Logging out

To disconnect a user (say that you’re for instance logging out and logging in as someone new) you can call the disconnect method and repeat the setUser call as someone else:

client.disconnect();
client.setUser(
    {
        id: 'jack',
        name: 'Jack Doe',
    },
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamFjayJ9.eljgjcOSorcR01nrYxcQtRUrHxT3ZyJlpu05Iv-3rd4',
);

Send Message

Below is a detailed example of how to send a message using Stream Chat:

const response = await channel.sendMessage({
    text: 'I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.',
    attachments: [
        {
            type: 'video',
            url: 'https://www.youtube.com/watch?v=IC-ZBJ-Kw2E',
            myCustomField: 123,
        },
    ],
    anotherCustomField: 234,
});

There are 3 built-in fields for the message:

Name Type Description Default Optional
text string The text of the chat message (Stream chat supports markdown and automatically enriches URLs).
attachments list A list of attachments (audio, videos, images, and text). Max is 10 attachments per message. Each attachment can have up to 5KB.
user object This value is automatically set in client-side mode. You only need to send this value when using the server-side APIs.
message custom data object Extra data for the message. Must not exceed 5KB in size.

Note that both the message and the attachments can contain custom fields. By default Stream’s frontend components support the following attachment types:

  • Audio
  • Video
  • Image
  • Text
You can specify different types as long as you implement the frontend rendering logic to handle them. Common use cases include:
  • Embedding products (photos, descriptions, outbound links, etc.)
  • Sharing of a users’ location
The React tutorial explains how to customize the Attachment component.

Message Format

When you post a message on a Channel, there are a few things that happen on the server:

  1. The text markdown format is parsed.
  2. The first URL mentioned in the text is enriched/unfurled. This gives you a preview of the images, videos, etc. from the open-graph data on the associated page.
  3. Any slash commands such as /giphy, /imgur, /ban, /flag etc. are handled.

Below is an example of URL enrichment as well as the resulting message structure:

Update & Remove a Message

You can edit a message by calling the updateMessage endpoint and including a message with an ID – the ID field is required when editing a message:

const message = { id: 123, text: 'the edited version of my text' };
const updateResponse = await client.updateMessage(message);

File Uploads

The channel.sendImage and channel.sendFile methods make it easy to upload files. Note that this functionality defaults to using the Stream CDN. If you would like, you can easily change the logic to upload to your own CDN of choice.

const promises = [
    channel.sendImage(
        fs.createReadStream('./helloworld.jpg'),
        'hello_world1.jpg',
    ),
    channel.sendImage(
        fs.createReadStream('./helloworld.jpg'),
        'hello_world2.jpg',
    ),
];
const results = await Promise.all(promises);
const attachments = results.map(response => {
    return {
        type: 'image',
        thumb_url: response.file,
        asset_url: response.file,
    };
});
const response = await channel.sendMessage({
    text: 'Check out what I have uploaded in parallel',
    attachments,
});
expect(response.message.attachments).to.equal(attachments);

In the example above, note how the message attachments are created after the files are uploaded. The React components support regular uploads, clipboard pasting, drag and drop, as well as URL enrichment via built-in open-graph scraping. As a bonus, the Stream CDN will automatically handle image resizing for you.

Send Reaction

Stream Chat has built-in support for user Reactions. Common examples are likes, comments, love, etc. Reactions can be customized so that you are able to use any type of Reaction your application requires.

Similar to other objects in Stream Chat, Reactions allow you to add custom data to the Reaction. This is helpful if you want to customize the Reaction logic. Custom data cannot be larger than 5KB.

const reaction = await channel.sendReaction(messageID, {
    type: 'love',
    myCustomField: 123,
});

You can remove Reactions by their id

const deleted = await channel.deleteReaction(reactionID);

Threads & Replies

Threads and replies provide your users with a way to go into more detail about a specific topic.

This can be very helpful to keep the conversation organized and reduce noise. Stream’s chat SDK supports threads, have a look at the example below:

const replyResponse = await channel.sendMessage({
    text: 'replying to a message',
    parent_id: parentID,
    show_in_channel: false,
});

If you specify show_in_channel, the message will be visible both in a thread of replies as well as the main Channel.

Search

Message search is built-in to the chat API. You can enable/disable the search indexing per chat type. The command shown below selects the channels in which John is a member. Next it searches the messages in those channels for the keyword “'supercalifragilisticexpialidocious'”:

const filters = { members: { $in: ['john'] } };
const response = await client.search(
   filters,
   'supercalifragilisticexpialidocious',
   { limit: 2, offset: 0 },
);

Pagination works via the standard limit and offset parameters. The first arguments, filters, uses a mongoose style query expression. Note that we don’t run Mongo on the backend, so only a subset of the standard filters are supported.

Event object

Events

The following events are used by Stream’s chat SDK:

Event Trigger Recipients
user.status.changed when a user status changes (eg. online, offline, away, ...) clients subscribed to the user status
user.watching.start when a user starts watching a channel clients watching the channel
user.watching.stop when a user stops watching a channel clients watching the channel
user.updated when a user is updated ???
typing.start sent when a user starts typing clients watching the channel
typing.stop sent when a user stops typing clients watching the channel
message.new when a new message is added on a channel clients watching the channel
message.updated when a message is updated clients watching the channel
message.deleted when a message is deleted clients watching the channel
message.read when a channel is marked as read clients watching the channel
message.reaction when a message reaction is added or deleted clients watching the channel
member.added when a member is added to a channel clients watching the channel
member.removed when a member is removed from a channel clients watching the channel
channel.updated when a channel is updated clients watching the channel
health.check every 30 second to confirm that the client connection is still active all clients
connection.changed when the state of the connection changed local event
connection.recovered when the connection to chat servers is back online local event
notification.message_new when a message is added to a channel clients that are not currently watching the channel
notification.mark_read when the total count of unread messages (across all channels the user is a member) changes clients from the user affected by the change
notification.invited when the user is invited to join a channel clients from the user invited
notification.invite_accepted when the user accepts an invite clients from the user invited
notification.added_to_channel when the user rejects an invite clients from the user invited
notification.removed_from_channel when a user is removed from a channel clients from the user invited

Event object

Name Type Description Optional
cid string Channel ID
type string Event type
message object Message object
reaction object Reaction object
channel object Channel object
member object User object for the channel member that was added/remvoed
user object User object of the current user
own_user object User object of the health check user
unread_count int the number of unread messages for current user
online int Number of users subscribed to a channel

Listening for events

As soon as you call watch on a Channel or call queryChannels you’ll start to listen to these events. You can hook into specific events:

channel.on('message.deleted', event => {
    console.log('event', event);
    console.log('channel.state', channel.state);
});

You can also listen to all events at once:

channel.on(event => {
    console.log('event', event);
    console.log('channel.state', channel.state);
});

Client events

Not all events are specific channels, events like: user status has changed, users' unread count has changed and other notifications are sent as client events. These events should be registered on the client directly, beside that work in the same way as channel events.

// subscribe to all client events and log the unread_count field 
client.on(event => {
   if (even.unread_count != null) {
      console.log(`unread messages count is now: ${event.unread_count}`);
   }
});

// the initial count of unread messages is returned by client.setUser
const chatStatus = await client.seUser(user, userToken);
console.log(`you have ${chatStatus.unread} unread messages`);

Connection events

The official SDKs make sure that a connection to Stream is kept alive at all times and that chat state is recovered when the user's internet connection comes back online. Your application can subscribe to changes to the connection using client events.

client.on('connection.changed', e => {
    if (e.online) {
        console.log('the connection is up!');
    } else {
        console.log('the connection is down!');
    };
});

Channel initialization

Here’s how you can initialize a single channel:

const conversation = authClient.channel('messaging', 'thierry-tommaso-1', {
    name: 'Founder Chat',
    image: 'http://bit.ly/2O35mws',
    members: ['thierry', 'tommaso'],
});

Channel init parameters

Name Type Description Optional
type string The channel type
id string The channel id
channel data object Extra data for the channel. Must not exceed 5KB in size.
The channel data can contain any number of custom fields. However, there are a few reserved fields that Stream uses.

Channel data reserved fields

Name Type Description
name string The Channel name. No special meaning, but by default the UI component will try to render this if the property is present.
image string The Channel image. Again there is no special meaning but by default, the UI components will try to render this property if it exists.
members array The members participating in this Channel. Note that you don’t need to specify members for a live stream or other public chat. You only need to specify the members if you want to limit access of this chat to these members and subscribe them to future updates

Watching a channel

Once you watch a channel, you will start receiving events for that channel. More info on that here.

To start watching a channel:

const conversationState = await conversation.watch();

Response schema

Name Type Description
config object The configuration for the channel type.
channel object The Channel object.
online integer Number of online members.
watchers object Users that are watching this channel. Represented as a mapping from the user id to the user object.
members object Channel members. Represented as a mapping from the user id to the user object.
read object Read messages grouped by user id. Represented as a mapping from the user id to the message object.
    
    curl -i -X POST -d "{\"state\":true,\"subscribe\":true}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso/query?api_key=YOUR_API_KEY"
    
    
Make sure that you call client.setUser() before channel.watch()

Unwatching

To stop watching a channel:

await conversation.stopWatching();
      
      curl -i -X POST -d "{\"user_id\":\"john\"}"\
      -H "Content-Type: application/json" \
      -H "Stream-Auth-Type: jwt" \
      -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiam9obiJ9.TxsU2JMbx7UXQ166lRabT6h7pAf415tLLKhJEZO_Kbg" \
      "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso/stop-watching?api_key=YOUR_API_KEY"
      
    

Querying a list of channels

If you’re building a similar application to Facebook Messenger or Intercom, you’ll want to show a list of Channels. For instance, the last conversations I participated in sorted by last_message_at. The Chat API supports MongoDB style queries.

Note that we don’t run MongoDB on the backend so only a subset of the query options is available.

Here’s an example of how you can query the list of Channels:

const filter = { members: { $in: ['thierry'] } };
const sort = { last_message_at: -1 };

const channels = await authClient.queryChannels(filter, sort, {
    watch: true,
    state: true,
});
for (const c of channels) {
    console.log(c.custom.name, c.cid);
}

Query parameters

Name Type Description Optional
filters object The query filters to use. You can query on any of the custom fields you’ve defined on the Channel. You can also filter on frozen, last_message_at, type, id, cid, roles and members
sort object The sorting method to use for the channels. You can sort based on last_message_at, updated_at, or created_at
options object Query options. See below

Query options

Name Type Description Default Optional
state boolean if true returns the Channel state True
watch boolean if true listen to changes to this Channel in real time
limit integer The number of channels to return (max is 30) 10
offset integer The offset. (max is 1000)

Response

The result of querying a channel is a list of ChannelStateobjects. The event system ensures that your local copy of the Channel state stays up to date. The state exposes the following information:

state.online = 0;
state.typing = Immutable({});
state.read = Immutable({});
state.messages = Immutable([]);
state.mutedUsers = Immutable([]);
state.watchers = Immutable({});
state.members = Immutable({});
Note that Channel state is immutable. It leverages seamless-immutable to prevent bugs caused by accidentally editing state.
    
    curl -i -g -X GET \
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc" \
    "https://chat-us-east-c1.stream-io-api.com/channels?api_key=YOUR_API_KEY&payload={\"filter_conditions\":{\"members\":{\"\$in\":[\"thierry\"]}},\"sort\":[{\"field\":\"last_message_at\",\"direction\":-1}],\"state\":true,\"subscribe\":true,\"watch\":true}"
    
    

Pagination

const result = await channel.query(
    { messages: { limit: 2, id_lte: 123 } },
    { members: { limit: 2, offset: 0 } },
    { watchers: { limit: 2, offset: 0 } },
);

As shown above, pagination for messages uses limit & id_lte parameters for pagination. The term id_lte stands for ID less than or equal. The ID-based pagination improves performance and prevents issues related to the list of messages changing while you’re paginating.

For members and watchers, we use limit and offset parameters.

    
    curl -i -X POST -d "{\"messages\":{\"limit\":2,\"id_lte\":123},\"members\":{\"limit\":2,\"offset\":0},\"watchers\":{\"limit\":2,\"offset\":0}}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso/query?api_key=YOUR_API_KEY"
    
    

Updating a channel

You can edit a Channel using the edit method:

const response = await channel.edit(
    {
        name: 'myspecialchannel',
        color: 'green',
    },
    { text: 'Thierry changed the channel color to green' },
);

Request params

Name Type Description
channel data object Object with the new channel information. One special field is frozen. If you set that to true new messages will be blocked.
message object Message object allowing you to show a system message in the Channel that something changed.
    
    curl -i -X POST -d "{\"message\":{\"text\":\"Thierry changed the channel color to green\"},\"data\":{\"name\":\"myspecialchannel\",\"color\":\"green\"}}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
    
    

Changing channel members

Adding/removing channel members

await channel.addMembers(['thierry', 'josh']);
await channel.removeMembers(['tommaso']);
    
    #add thiery
    curl -i -X POST -d "{\"add_members\":[\"thierry\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"

    #remove tommaso
    curl -i -X POST -d "{\"remove_members\":[\"tommaso\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
    
    
Note: You can only add/remove up to 100 members at once.

Adding/removing moderators to a channel

These operations can only be performed server-side.

Adds users as moderators(or updates their role to moderator if already members) or removes the moderator status

await channel.addModerators(['thierry', 'josh']);
await channel.demoteModerators(['tommaso']);
    
    #add thiery
    curl -i -X POST -d "{\"add_moderators\":[\"thierry\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXJ2ZXJzaWRlIjp0cnVlfQ.OYCKMIZtQ4xS9Ial_PfHcdmZgVoz0_UAjTryXpBEw44" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"

    #remove tommaso
    curl -i -X POST -d "{\"demote_moderators\":[\"tommaso\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXJ2ZXJzaWRlIjp0cnVlfQ.OYCKMIZtQ4xS9Ial_PfHcdmZgVoz0_UAjTryXpBEw44" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
    
    
Note: You can only add/remove up to 100 moderators at once.

Channel Invites

Inviting users

Users can be invited to a channel. When that happen, their client will receive a notification event.

const conversation = authClient.channel('messaging', 'awesome-chat', {
    name: 'Founder Chat',
    members: ['thierry', 'tommaso'],
    invites: ['nick'],
});

await conversation.create();

Accepting an invite

// nickClient is Nick's client 
const nickChannel = nickClient.channel('messaging', 'awesome-chat');

await nickChannel.acceptInvite({
    message: { text: 'Nick accepted the chat invite.' },
});

// start watching the channel as usual
await nickChannel.watch();

Request params

Name Type Description
message object Message object allowing you to show a system message in the Channel that something changed.

Rejecting an invite

await nickChannel.rejectInvite();

Delete a channel

You can delete a Channel using the delete method:

await nickChannel.delete();
      
        curl -i -X DELETE
        -H "Content-Type: application/json" \
        -H "Stream-Auth-Type: jwt" \
        -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
        "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
      
    

Features

There are 5 built-in channel types:

  • livestream: Sensible defaults in case you want to build chat like YouTube or Twitch.
  • messaging: Configured for apps such as Whatsapp or Messenger.
  • gaming: Configured for in-game chat.
  • commerce: Good defaults for building something like your own version of Intercom or Drift.
  • team: If you want to build your own version of Slack or something similar start here.

As you can see in the examples below you can define your own Channel types and configure them to fit your needs. The Channel type allows you to configure these features:

  • typing_events: Controls if typing indicators are shown. Enabled by default.
  • read_events: Controls whether the chat shows how far you’ve read. Enabled by default.
  • connect_events: Determines if events are fired for connecting and disconnecting to a chat. Enabled by default.
  • search: Controls if messages should be searchable (this is a premium feature). Disabled by default.
  • reactions: If users are allowed to add reactions to messages. Enabled by default.
  • replies: Enables message threads and replies. Enabled by default.
  • mutes: Determines if users are able to mute other users. Enabled by default.

It allows you to specify these settings:

Name Type Description Default
automod string Disabled, simple or AI are valid options for the Automod (AI based moderation is a premium feature). simple
message_retention string A number of days or infinite infinite
max_message_length int The max message length 5000
commands list An array of strings. all, fun_set, moderation_set or the names of individual commands. I.E. giphy, imgur, ban, flag and mute. ['all']

Note: you need to use server side authentication to create, edit or delete a channel type.

Creating a Channel Type

await client.createChannelType({
    name: 'public', commands: ['fun_set'], permissions:[
        new Permission(
            name: 'Allow reads for all',
            priority: 999,
            resources: ['ReadChannel', 'CreateMessage'],
            AnyRole,
        ),
        DenyAll
    ],
    mutes: false,
    reactions: false
});

If not provided, the permission policies will default to the ones from the built-in team type.

List Channel Types

You can retrieve the list of all channel types defined for your application.

await client.listChannelTypes();

Get a Channel Type

You can retrieve a channel type definition with this endpoint. Note: features and commands are also returned by other channel endpoints.

client.getChannelType('public');

Edit a Channel Type

Channel type features, commands and permissions can be changed. Only the fields that must change need to be provided, fields that are not provided to this API will remain unchanged.

client.updateChannelType('public', {
    permissions: [AllowAll, DenyAll],
    replies: false,
    commands: ["all"]
});
    
    curl -i -X PUT -d "{\"permissions\": [\"AllowAll\", \"DenyAll\"], \"replies\": false}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXJ2ZXJzaWRlIjp0cnVlfQ.OYCKMIZtQ4xS9Ial_PfHcdmZgVoz0_UAjTryXpBEw44" \
    "https://chat-us-east-c1.stream-io-api.com/channeltypes/public?api_key=YOUR_API_KEY"
    
  

Features of a channel can be updated by passing the boolean flags:

client.updateChannelType("public", {
  typing_events: false,
  read_events: true,
  connect_events: true,
  search: false,
  reactions: true,
  replies: false,
  mutes: true
});

Settings can also be updated by passing in the desired new values:

client.updateChannelType("public", {
  automod: "disabled",
  message_retention: "7",
  max_message_length: 140,
  commands: ["imgur", "giphy", "ban"]
});

Permissions for a channel type can be updated either by using predefined values or creating new ones:

client.updateChannelType("public", {
    permissions: [
        new Permission(
            name: 'Allow reads for all',
            priority: 999,
            resources: ['ReadChannel', 'CreateMessage'],
            AnyRole
        ),
        DenyAll
    ]
});

Remove a Channel Type

client.deleteChannelType('public');

Note: you cannot delete a channel type if there are any active channels of that type.

Permissions

The channel type also allows you to specify the permissions your chat can use. If not provided, the permissions will default to team’s channel type permissions.

The Channel object allows you to define a list of moderators and admins. The user object allows you to mark a user as a moderator or admin for your entire application.

Not that you can only modify these fields via the backend API:

// editing a channel
const data = {
    image: image,
    created_by: elon,
    roles: { elon: 'admin', gwynne: 'moderator' },
};
const spacexChannel = serverClient.channel('organization', 'spacex').edit(data);

You can edit a user like this:

await serverClient.editUser({
    id: 'tommaso',
    name: 'Tommaso Barbugli',
    role: 'admin',
});

Server-side, you’re allowed to do everything. For client-side integrations, the permission system kicks in. This system basically specifies a list of permissions the given user is allowed to do.

Note: changing user roles is only allowed server-side.

Permission checking is performed taking into account parameters:

  • API Request: the action the user is performing (e.g. send a message, edit a message)

Channel Type

Most API requests are specific to a channel (eg. add a message to channel “livestream:rockets”). Channel types have different permissions and can be configured via APIs. The 5 default channel types come with good default permission policies. You can find more information on how to manage permissions in the Channel Types section.

User Role & User Channel Role

  1. admin: This role allows users to perform more advanced actions. This role should be granted only to staff users.
  2. moderator: Allows users to perform moderation (e.g. ban users, add/remove users, …).
  3. user: This is the default role assigned to any user.
  4. channel member: A user that is also listed as member on a channel.
  5. guest: Users added using setGuestUser get this role automatically.
  6. anonymous: Users added using setAnonymousUser get this role automatically.

Note: a user can have the moderator role for some specific channels and the user role for all other channels.

Object Ownership

If applicable, ownership of the entity is taken into account. This parameter allows you to grant users the ability to edit their own message while denying editing others’ messages. Permission policies are organized as list ordered by priority. A permission policy has the following fields:

Channel Type

The channel the policy applies to.

Name

The unique name for the policy (eg. "Channel member permissions").

Resources

The list of API resources the policy applies to. Policies can match one more resources by their name (see full list below) or any resource by using the wildcard resource value “*”.

Roles

The list of roles the policy applies to. This value can be empty in that case the user role is not going to be taken into account.

Owner

Whether the policy should be applied to requested that alter an object owned by the requesting user. This field is either true or false.

Action

The action to apply to the API request once resources, roles and action fields are matching. The two allowed values are: Allow and Deny.

Priority

The priority of the policy. Policies are evaluated ordered by their priority, this field allows you to create a stable order.

Default Permission Policies

The five built-in channel types ship with default permission setting.

Messaging

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes Yes No No Yes -
Read Channel Yes Yes No No No Yes -
Update Channel Members Yes Yes No No No No Yes
Update Channel Roles No No No No No No -
Update Channel Yes Yes No No No No Yes
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User No No No No No No Yes
Upload Attachment Yes Yes No No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No Yes -
Add Links Yes Yes No No No Yes -

Livestream

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes Yes No No Yes -
Read Channel Yes Yes Yes Yes Yes Yes -
Update Channel Members Yes No No No No No -
Update Channel Roles Yes No No No No No -
Update Channel Yes No No No No No -
Create Message Yes Yes Yes No No Yes -
Update Message Yes Yes No No No No -
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User No No No No No No Yes
Upload Attachment Yes Yes Yes No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes Yes No No Yes -
Add Links Yes Yes Yes No No Yes -

Gaming

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes No No No No No -
Read Channel Yes Yes No No No Yes Yes
Update Channel Members Yes No No No No No -
Update Channel Roles Yes No No No No No -
Update Channel Yes No No No No No -
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role Yes No No No No No -
Edit User Yes No No No No No Yes
Upload Attachment Yes Yes No No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No Yes -
Add Links Yes Yes No No No Yes -

Commerce

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes No Yes No Yes -
Read Channel Yes Yes No No No Yes Yes
Update Channel Members Yes Yes No No No No Yes
Update Channel Roles No No No No No No -
Update Channel Yes Yes No No No No -
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User Yes Yes No No No No Yes
Upload Attachment Yes Yes No Yes No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No No -
Add Links Yes Yes No Yes No Yes -

Team

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes Yes No No Yes -
Read Channel Yes Yes No No No Yes Yes
Update Channel Members Yes Yes No No No No Yes
Update Channel Roles No No No No No No -
Update Channel Yes Yes No No No No Yes
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User No No No No No No Yes
Upload Attachment Yes Yes No No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No Yes -
Add Links Yes Yes No No No Yes -

Commands

By default the following commands are supported:

  • /giphy query
  • /imgur query
  • /ban @userid reason
  • /unban @userid
  • /mute @userid
  • /unmute @userid
  • /flag @userid

Additionally, it’s possible to add your own commands. Commands are implemented using actions and attachments. Let’s have a look what data is returned when you run /giphy rock

{
    "args": "rock",
    "attachments": [
        {
            "type": "image",
            "thumb_url": "https://media0.giphy.com/media/3o85xx69HG11jP4QI/giphy.gif",
        }
        "actions": [
            {
                "name": "image_action",
                "text": "Send",
                "style": "primary",
                "type": "button",
                "value": "send"
            },
            {
                "name": "image_action",
                "text": "Shuffle",
                "style": "default",
                "type": "button",
                "value": "shuffle"
            },
            {
                "name": "image_action",
                "text": "Cancel",
                "style": "default",
                "type": "button",
                "value": "cancel"
            }
        ]
    ],
    "command": "giphy",
    "created_at": "2018-11-30T21:25:04.147725Z",
    "html": "<p>/gihpy rock</p>\n",
    "id": "33",
    "reactions": [],
    "reply_count": 0,
    "status": "received",
    "text": "/giphy rock",
    "type": "ephemeral",
    "updated_at": "2018-11-30T21:25:04.147725Z",
    "user": {
        "id": "thierry",
        "image": "myimageurl",
        "name": "Thierry",
        "role": "admin",
        "status": "busy"
    }
}

Actions

Actions have a name, text, style, type and value attributes. These simple fields allow you to build complex interactions with your bots & slash commands. All of the available Stream UI Components know how to render these actions. So for /giphy ocean, the result will be this:

Presence

User presence allows you to show when a user was last active and if they are online right now. Whenever you read a user the data will look like this:

Data Format

{
    id: 'myuserid',
    online: true,
    status: 'eating a burger',
    last_active: '2019-01-07T13:17:42.375Z'
}

The online field indicates if the user is online. The status field just stores a text indicating the current user status.

Invisible

To mark a user invisible simply set the invisible property to true. You can also set a custom status message at the same time:

// mark a user as invisible
authClient.setUser({
    id: 'myuserid',
    invisible: true
});

When invisible is set to true the current user will appear as offline to other users.

Listening to User Presence Changes

Of course, you want to listen to the user presence changes. This allows you to show a user as offline when they leave and update their status in real time. These 3 endpoints allow you to watch user presence:

// 3 ways of watching user presence

// If you pass presence: true to channel.watch it will watch the list of user presence changes.
// Note that you can listen to at most 10 users using this API call
const channel = authClient.channel('messaging', 'my-conversation-123', {
    members: ['john', 'jack'],
    color: 'green'
})
const state = await channel.watch({presence: true})

// queryChannels allows you to listen to the members of the channels that are returned
// so this does the same thing as above and listens to online status changes for john and jack
const channels = await authClient.queryChannels(
    {color: 'green'},
    {last_message_at: -1},
    {presence: true},
)

// queryUsers allows you to listen to user presence changes for john and jack
const users = await authClient.queryUsers({id: {$in: ['john', 'jack']}}, {id: -1}, {presence: true})

Moderation

Flag

Any user is allowed to flag a message or a user.

const data = await authClient.flagMessage(messageResponse.message.id);

Mute

Any user is allowed to mute another user.

const data = await authClient.muteUser('eviluser');

Ban

Users can be banned from an app entirely or from a channel. When a user is banned, it will be not allowed to post messages until the ban is removed or expired. In most cases only admins or moderators are allowed to ban other users from a channel.

Name Type Description Default Optional
timeout number The timeout in minutes until the ban is automatically expired. no limit
reason string The reason the ban was created.

Note: Banning a user from all channels can only be done using server-side auth.

// ban a user for 60 minutes from all channel
let data = await authClient.banUser('eviluser', {
    timeout: 60,
    reason: 'Banned for one hour',
});

// ban a user from the livestream:fortnite channel
data = await channel.banUser('eviluser', {
    reason: 'Profanity is not allowed here',
});

// remove ban from channel
data = await channel.unbanUser('eviluser');

// remove global ban
data = await authClient.unbanUser('eviluser');

Automod

For livestream and gaming channel types Automod is enabled by default. There are three options for moderation:

  • disabled
  • simple (blocks messages that contain a list of profane words)
  • ai (uses an AI-based classification system to detect various types of bad content)

You can configure the thresholds for the AI-based system by specifying a value between 0 and 1 for the following types of bad content. As an example, if you set the value for Spam to 0.7 it will block any message that seems very likely to be spam. On the other hand, if you set a value of 0.3 it will be very sensitive and block anything that remotely looks like spam.

  • Spam:
    • Unsolicited messages: typically used as clickbait for financial gain from bot messages.
  • Toxic:
    • Not a constructive comment, or in general not nice.
    • Ex: "This is dumb" would be classified highly as toxic and only slightly obscene.
  • SevereToxic:
    • Worst of the worst. Typically insulting, obscene and degrading.
    • Ex: "What a motherfucking piece of crap those fuckheads for blocking us!"
  • Obscene:
    • Generally reserved for inappropriate words. Typically offensive to accepted standards of morality.
  • Threat:
    • A direct threat to another user.
    • Ex: "I will spit on you" would classified highly as toxic and a threat but only slightly obscene.
  • Insult:
    • Insulting another user.
    • Ex: "You are dumb" would be classified as toxic and insulting, yet only moderately obscene.
  • IdentityHate:
    • Degradation of a protected class.

AI-based moderation is a premium feature. Please contact sales to discuss your options.

Server-side API Usage

Here’s an example of sending a chat message from a server-side integration:

const options = {};
const serverClient = new StreamChat('key', 'secret', options);
const spacexChannel = serverClient.channel('organization', 'spacex', {
    image: image,
    created_by: elon,
});
const createResponse = await spacexChannel.create();
const text = 'I was gonna get to mars but then I got high';
const message = {
    text,
    user: elon,
}
const response = await spacexChannel.sendMessage(message);

Note the 3 differences compared to the client side integration:

  1. Initialize the client with the server side secret.
  2. Create the Channel instead of initializing it (we don't want to retrieve the state or subscribe to changes in this case).
  3. Pass the user parameter if the endpoint requires it (eg. creating a channel or a message)
const tommaso = { id: "tommaso" };

// create a channel and assign user Tommaso as owner
const channel = serverClient.channel("team", "stream", { created_by: tommaso })
await channel.create();

// send a message for tommaso
const message = {
   "hi there!",
   user: tommaso,
};
await channel.sendMessage(message);

Role management

Change a user role

await serverClient.updateUser({
    id: 'tommaso',
    name: 'Tommy Doe',
    role: 'admin',
});

Add moderators to a channel

let channel = serverClient.channel("livestream", "fortnite");
await channel.addModerators(["thierry", "tommaso"]));

Remove moderators from a channel

await channel.demoteModerators(["thierry"]));

Authentication

Enabling development tokens

Token validation can be disabled for development apps. This allows you to use development tokens and work on user token provisioning later on.

// disable auth checks, allows dev token usage
await client.updateAppSettings({
    disable_auth_checks: true,
});

// re-enable auth checks
await client.updateAppSettings({
    disable_auth_checks: false,
});

Disable permission checking

By default all apps ship with role based permission checks. During development you can decide to turn off permission checks, this way all users will act as admin users.

// disable permission checks
await client.updateAppSettings({
	disable_permissions_checks: true,
});

// re-enable permission checks
await client.updateAppSettings({
	disable_permissions_checks: false,
});

Webhooks

By using webhooks, you can receive all events within your application. When configured, every event happening on Stream Chat will propagate to your webhook endpoint via a HTTP POST request.

Webhook can help you migrating from a different chat provider to Stream without disruption or to support complex notification mechanisms (eg. send an SMS to an offline user when a direct message is sent).

Webhook requirements

In order to use webhooks, the endpoint responding to the URL must:

  • Be reachable from public internet, tunneling services like ngrok are supported
  • Respond with a 200 HTTP code in less than 3 seconds
  • Handle HTTP requests with POST body
  • Able to parse JSON payloads
  • Support HTTP/1.1

While not required, we recommend to follow these best-practices for production environments:

  • Use SSL over HTTP using a certificate from a trusted authority (eg. Letsencrypt)
  • Verify the x-signature header
  • Support Keep-Alive
  • Be highly available
  • Offload the processing of the message (read, store and forget)

Configuration

// stream chat:settings:push --nick=yes

Verify events via X-Signature

All HTTP requests can be verified as coming from Stream (and not tampered by a 3rd party) by analyzing the signature attached to the request. Every request includes an HTTP header called "x-signature" containing a cryptographic signature of the message. Your webhook endpoint can validate that payload and signature match.

// body is the request data as a string
client.verifyWebhook(body, req.headers['x-signature']);
import stream_chat

client = stream_chat.connect('API_KEY', 'API_SECRET')

# Django request
client.verify_webhook(request.body, request.META['HTTP_X_SIGNATURE'])

# Flask request
client.verify_webhook(request.data, request.headers['X-SIGNATURE'])
# TODO: add check for x-signature check
// TODO: add check for x-signature check
// TODO: add check for x-signature check
// TODO: add check for x-signature check
// TODO: add check for x-signature check

Webhook events

Below you can find the complete list of events that are sent via web-hooks together with the description of the data payload.

For message and channel events the webhook request body will also include the list of channel members with their user information and their total count of unread messages. For performance reasons, such list is only included for channels with up to 25 members.

Event Triggered
message.new when a new message is added
message.reminder when a user has an unread message on a channel for longer than ten minutes. the ten minutes tracking is restarted once the user marks the channel as read.
message.updated when a message is updated
message.deleted when a message is deleted
message.reaction when a message reaction is added or deleted
member.added when a member is added to a channel
member.removed when a member is removed from a channel
channel.updated when a channel is updated

message.new

{
    "cid": "messaging:fun",
    "type": "message.new",
    "message": {
        "id": "2fd3e763-4e31-4757-9278-537a48549c3e",
        "text": "6e7f7dff-97d3-4abe-af89-ee3bb828bb5c",
        "html": "<p>6e7f7dff-97d3-4abe-af89-ee3bb828bb5c</p>\n",
        "type": "regular",
        "user": {
            "id": "a1e5294c-9c70-4e93-8495-12b9c1f40ec2",
            "role": "user",
            "created_at": "2019-03-22T11:15:39.289531Z",
            "last_active": "2019-03-22T12:15:39.854201+01:00",
            "online": true
        },
        "attachments": [],
        "latest_reactions": [],
        "own_reactions": [],
        "reaction_counts": null,
        "reply_count": 0,
        "created_at": "2019-03-22T11:15:39.875849Z",
        "updated_at": "2019-03-22T11:15:39.875849Z"
    },
    "user": {
        "id": "a1e5294c-9c70-4e93-8495-12b9c1f40ec2",
        "role": "user",
        "created_at": "2019-03-22T11:15:39.289531Z",
        "last_active": "2019-03-22T12:15:39.854201+01:00",
        "online": true
    },
    "online": 1,
    "created_at": "2019-03-22T12:15:39.879925+01:00",
    "members": [
        {
            "user_id": "a1e5294c-9c70-4e93-8495-12b9c1f40ec2",
            "user": {
                "id": "a1e5294c-9c70-4e93-8495-12b9c1f40ec2",
                "role": "user",
                "created_at": "2019-03-22T11:15:39.289531Z",
                "last_active": "2019-03-22T12:15:39.854201+01:00",
                "online": true,
                "unread_count": 1
            },
            "is_moderator": false,
            "invited": false,
            "invite_accepted_at": null,
            "invite_rejected_at": null,
            "created_at": "2019-03-22T11:15:39.620822Z",
            "updated_at": "2019-03-22T11:15:39.620822Z"
        }
    ]
}

message.reminder


          The example "chat_webhook_payload_message_reminder" couldn't be loaded
 	  		

message.updated

{
    "cid": "messaging:fun",
    "type": "message.updated",
    "message": {
        "id": "ffcc0a23-18ce-45ca-89d5-673d00bbf5af",
        "text": "new stuff",
        "html": "<p>new stuff</p>\n",
        "type": "regular",
        "user": {
            "id": "f62ed067-7580-478a-a815-19abad8fdd23",
            "role": "user",
            "created_at": "2019-03-22T13:16:15.105815Z",
            "updated_at": "0001-01-01T00:00:00Z",
            "online": false
        },
        "attachments": [],
        "latest_reactions": [],
        "own_reactions": [],
        "reaction_counts": null,
        "reply_count": 0,
        "created_at": "2019-03-22T13:16:15.598145Z",
        "updated_at": "2019-03-22T13:16:15.819981Z"
    },
    "user": {
        "id": "f62ed067-7580-478a-a815-19abad8fdd23",
        "role": "user",
        "created_at": "2019-03-22T13:16:15.105815Z",
        "last_active": "2019-03-22T14:16:15.578372+01:00",
        "online": true
    },
    "created_at": "2019-03-22T14:16:15.832389+01:00"
}

message.deleted

{
    "cid": "messaging:fun",
    "type": "message.deleted",
    "message": {
        "id": "1bfef211-c348-4929-8303-79f021291cf5",
        "text": "new stuff",
        "html": "<p>new stuff</p>\n",
        "type": "regular",
        "user": {
            "id": "308ac16b-bd87-4e9f-a0d8-d58a380c2455",
            "role": "user",
            "created_at": "2019-03-22T13:26:12.396254Z",
            "updated_at": "0001-01-01T00:00:00Z",
            "online": false
        },
        "attachments": [],
        "latest_reactions": [],
        "own_reactions": [],
        "reaction_counts": {},
        "reply_count": 0,
        "created_at": "2019-03-22T13:26:13.095262Z",
        "updated_at": "2019-03-22T13:26:13.804363Z",
        "deleted_at": "2019-03-22T13:26:14.015453Z"
    },
    "created_at": "2019-03-22T14:26:14.058703+01:00"
}

message.reaction

{
    "cid": "messaging:fun",
    "type": "message.reaction",
    "message": {
        "id": "d13a1b23-e372-469f-8366-ad78840a6aaa",
        "text": "new stuff",
        "html": "<p>new stuff</p>\n",
        "type": "regular",
        "user": {
            "id": "2f465992-2595-4438-b266-67a89c44d3fe",
            "role": "user",
            "created_at": "2019-03-22T13:24:31.03696Z",
            "updated_at": "0001-01-01T00:00:00Z",
            "online": false
        },
        "attachments": [],
        "latest_reactions": [
            {
                "message_id": "d13a1b23-e372-469f-8366-ad78840a6aaa",
                "user": {
                    "id": "2f465992-2595-4438-b266-67a89c44d3fe",
                    "role": "user",
                    "created_at": "2019-03-22T13:24:31.03696Z",
                    "last_active": "2019-03-22T14:24:31.664103+01:00",
                    "online": true
                },
                "type": "lol",
                "created_at": "2019-03-22T13:24:32.160981Z"
            }
        ],
        "own_reactions": [],
        "reaction_counts": {
            "lol": 1
        },
        "reply_count": 0,
        "created_at": "2019-03-22T13:24:31.690387Z",
        "updated_at": "2019-03-22T13:24:32.162482Z"
    },
    "reaction": {
        "message_id": "d13a1b23-e372-469f-8366-ad78840a6aaa",
        "user": {
            "id": "2f465992-2595-4438-b266-67a89c44d3fe",
            "role": "user",
            "created_at": "2019-03-22T13:24:31.03696Z",
            "last_active": "2019-03-22T14:24:31.664103+01:00",
            "online": true
        },
        "type": "lol",
        "created_at": "2019-03-22T13:24:32.160981Z"
    },
    "user": {
        "id": "2f465992-2595-4438-b266-67a89c44d3fe",
        "role": "user",
        "created_at": "2019-03-22T13:24:31.03696Z",
        "last_active": "2019-03-22T14:24:31.664103+01:00",
        "online": true
    },
    "created_at": "2019-03-22T14:24:32.168738+01:00"
}

member.added

{
    "cid": "messaging:fun",
    "type": "member.added",
    "member": {
        "user_id": "851e652d-55f3-4e01-a155-de0bbca1851f",
        "user": {
            "id": "851e652d-55f3-4e01-a155-de0bbca1851f",
            "role": "user",
            "created_at": "2019-03-22T13:27:09.496307Z",
            "online": false
        },
        "is_moderator": false,
        "invited": false,
        "invite_accepted_at": null,
        "invite_rejected_at": null,
        "created_at": "2019-03-22T13:27:11.197558Z",
        "updated_at": "2019-03-22T13:27:11.197558Z"
    },
    "user": {
        "id": "851e652d-55f3-4e01-a155-de0bbca1851f",
        "role": "user",
        "created_at": "2019-03-22T13:27:09.496307Z",
        "online": false
    },
    "created_at": "2019-03-22T14:27:11.206125+01:00"
}

member.updated

{
    "cid": "messaging:fun",
    "type": "member.updated",
    "member": {
        "user_id": "6aa8b0d8-74f2-4a90-a5fd-c93ec1933555",
        "user": {
            "id": "6aa8b0d8-74f2-4a90-a5fd-c93ec1933555",
            "role": "user",
            "created_at": "2019-03-22T13:31:09.077966Z",
            "online": false
        },
        "is_moderator": true,
        "invited": false,
        "invite_accepted_at": null,
        "invite_rejected_at": null,
        "created_at": "2019-03-22T13:31:10.827627Z",
        "updated_at": "2019-03-22T13:31:11.040197Z"
    },
    "user": {
        "id": "6aa8b0d8-74f2-4a90-a5fd-c93ec1933555",
        "role": "user",
        "created_at": "2019-03-22T13:31:09.077966Z",
        "online": false
    },
    "created_at": "2019-03-22T14:31:11.048619+01:00"
}

member.removed

{
    "cid": "messaging:fun",
    "type": "member.removed",
    "user": {
        "id": "6585dbbb-3d46-4943-9b14-a645aca11df4",
        "role": "user",
        "created_at": "2019-03-22T14:22:04.581208Z",
        "online": false
    },
    "created_at": "2019-03-22T15:22:07.040496+01:00"
}

channel.updated

{
    "cid": "messaging:fun-22d999c5-b728-4608-9e24-17de9a3ba9f4",
    "type": "channel.updated",
    "message": {
        "id": "3bc740cc-ba95-4bba-9e02-31c3dccad0cd",
        "text": "626be8d8-febc-4a1a-9ca7-6645aee3fcd1",
        "html": "<p>626be8d8-febc-4a1a-9ca7-6645aee3fcd1</p>\n",
        "type": "system",
        "user": {
            "id": "tommaso-8389be23-1dea-4072-b629-c8f235ec2c4a",
            "role": "",
            "created_at": "0001-01-01T00:00:00Z",
            "online": false
        },
        "attachments": [],
        "latest_reactions": [],
        "own_reactions": [],
        "reaction_counts": null,
        "reply_count": 0,
        "created_at": "2019-03-22T14:23:27.560447Z",
        "updated_at": "2019-03-22T14:23:27.560447Z",
        "custom_stuff": "bananas"
    },
    "channel": {
        "cid": "messaging:fun",
        "id": "fun",
        "type": "messaging",
        "last_message_at": "2019-03-22T14:23:25.562837Z",
        "created_by": {
            "id": "8389be23-1dea-4072-b629-c8f235ec2c4a",
            "role": "user",
            "created_at": "2019-03-22T14:23:24.977103Z",
            "updated_at": "0001-01-01T00:00:00Z",
            "online": false
        },
        "frozen": false,
        "member_count": 1,
        "config": {
            "created_at": "2016-08-18T18:42:30.586808+02:00",
            "updated_at": "2016-08-18T18:42:30.586808+02:00",
            "name": "messaging",
            "typing_events": true,
            "read_events": true,
            "connect_events": true,
            "search": true,
            "reactions": true,
            "replies": true,
            "mutes": true,
            "message_retention": "infinite",
            "max_message_length": 5000,
            "automod": "disabled",
            "commands": [
                "giphy",
                "imgur",
                "flag",
                "ban",
                "unban",
                "mute",
                "unmute"
            ]
        },
        "awesome": "yes yes"
    },
    "created_at": "2019-03-22T15:23:27.605997+01:00"
}

channel.deleted

{
    "cid": "messaging:fun",
    "type": "channel.deleted",
    "channel": {
        "cid": "messaging:fun",
        "id": "fun",
        "type": "messaging",
        "deleted_at": "2019-03-22T14:24:33.46245Z",
        "frozen": false,
        "member_count": 1,
        "config": {
            "created_at": "2016-08-18T18:42:30.586808+02:00",
            "updated_at": "2016-08-18T18:42:30.586808+02:00",
            "name": "messaging",
            "typing_events": true,
            "read_events": true,
            "connect_events": true,
            "search": true,
            "reactions": true,
            "replies": true,
            "mutes": true,
            "message_retention": "infinite",
            "max_message_length": 5000,
            "automod": "disabled",
            "commands": [
                "giphy",
                "imgur",
                "flag",
                "ban",
                "unban",
                "mute",
                "unmute"
            ]
        }
    },
    "created_at": "2019-03-22T15:24:33.46735+01:00"
}

Push notification

Push Notifications for iOS

Using the APNs, your users' apps can receive push notifications directly on their client app for new messages when offline. Stream supports both Certificate-based provider connection trust(.p12 certificate), as well as Token-based provider connection trust(JWT).

Setup APN push using token authentication

Token based authentication is the preferred way to setup push. Token based is very easy to setup and guarantees strong security.

Step 1. Retrieve your TeamID

Sign in to your Apple Developer Account and then navigate to Membership. Copy your Team ID and store it somewhere safe.

Download

Step 2. Generate a token

  1. From your Apple Developer Account overview, navigate to Certificates, Identifiers & Providers

    Certificate Dialog
  2. Make sure iOS, tvOS, watchOS is selected on the navigation pane on the left, and go to Keys > All

    Select Key Dialog
  3. Click on the + button to Add a new key

    Add Key Dialog
  4. In the Name field input a name for your key. In the Key Services section, select Apple Push Notifications service (APNs) and then click on Continue

    Add Key Dialog
  5. Review the information from the previous step and click on Confirm

  6. Copy your Key ID and store it somewhere safe.

  7. Save the key on your hard drive. Note:You can only dowload your key now. If you lose the key, you will have to repeat step 2

    Add Key Dialog

Step 3. Upload the Key credentials to Stream chat

Upload the TeamID, KeyID and Key from the previous steps.

// stream chat:settings:push --nick=yes

Setup APN push using certificate authentication

If token based authentication is not an option, you can setup APN with certificate authentication. You will need to generate a valid p12 certificate for your application and upload it to Stream.

Step 1. Create a Certificate Signing Request(CSR)

  1. On your Mac, open Keychain Access
  2. Go to Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.

    Request Certificate
  3. Fill out the information in the Certificate Information window as specified below and click "Continue."

    • In the User Email Address field, enter the email address to identify with this certificate
    • In the Common Name field, enter your name
    • In the Request group, click the "Saved to disk" option
    Certificate Dialog
  4. Save the file on your hard drive.(E.g.: /Users/john/Downloads/CertificateSigningRequest.certSigningRequest)

Step 2. Create a Push Notification SSL certificate

  1. Sign in to your Apple Developer Account and then navigate to Certificates, Identifiers & Providers

    Certificate Dialog
  2. Make sure iOS, tvOS, watchOS is selected on the navigation pane on the left, and go to Certificates > All

    Select Certificate Dialog
  3. Click on the + button to Add a new certificate

    Add Certificate Dialog
  4. In the Development section, select Apple Push Notification service SSL (Sandbox) and then click on Continue

    Certificate Type
  5. Select your app in the dropdown list and then click on Continue

  6. You will see instructions on how to generate a .certSigningRequest file. This was already covered in the previous section. Click on Continue

  7. Click on Choose File and then navigate to where you have saved the .certSigningRequest file from the previous section (E.g.: /Users/john/Downloads/CertificateSigningRequest.certSigningRequest). Click on Continue

  8. Click on Download to save your certificate to your hard drive.(E.g.: /Users/john/Downloads/aps_development.cer)

    Download

Step 3. Export the certificate in .p12 format

  1. On your mac, navigate to where you have saved the .cer file from the previous section (E.g.: /Users/john/Downloads/aps_development.cer) and double click on the file. This will add it to your Keychain

  2. Go to Keychain Access

  3. At the top left, select Keychains > login.

    Then, at the bottom left, select Category > Certificates

    Download
  4. Select the certificate you've created in the previous step. It should look like Apple Development IOS Push Services: YOUR_APP_NAME and expand it to see the private key(it should be named after the Name you provided when creating the Certificate Signing Request: in the case of this example: John Smith)

    Download
  5. Right-click the private key and click on Export. In the File format section select Personal Information Exchange (.p12) and save the file on your hard drive

    Download

Step 4. Upload the certificate to Stream chat

Coming soon!

// stream chat:settings:push --nick=yes

Message payload

Stream offers basic templating (via ) for the apn payload.

Supported variables

Name Type Description
channel object Channel object. You can access the channel name and any other custom field you have defined for ths channel
sender object Sender object. You can access the user name or any other custom field you have defined for the user
message object Message object. You can acess the text of the message (or a preview of it if the message is too large) or any other custom field you have defined for the message
unread_count integer Number of unread messages

Note:If you do not provide a payload template for APNs, the default one will be used:

{
    "aps" : {
        "alert" : {
            "title" : "{{ sender.name }}",
            "subtitle" : "New direct message from {{ sender.name }}",
            "body" : "{{ message.text }}"
        },
        "badge": "{{ unread_count }}",
        "category" : "NEW_MESSAGE"
    }
}
Note how this uses unread_count together with badges to show the unread messages count in the app icon:

Here is another example of using variable interpolation:

{
    "aps" : {
        "alert" : {
            "title" : "{{ channel.name }}",
            "subtitle" : "{{ unread_count }} new message(s) from {{ sender.name }}",
            "body" : "{{ message.text }}"
        },
        "category" : "NEW_MESSAGE"
    }
}

The payload that will be sent to APNs will look like this:

{
    "aps" : {
        "alert" : {
            "title" : "Chat with Mike",
            "subtitle" : "3 new message(s) from Mike",
            "body" : "Hey why aren't you answering"
        },
        "category" : "NEW_MESSAGE"
    }
}

Push notification

Push Notifications for Android and Web

Using Firebase, your users' apps can receive push notifications directly on their client app for new messages when offline.

// stream chat:settings:push --nick=yes

Message payload

Stream offers basic templating (via ) for the firebase payload.

Supported variables

Name Type Description
channel object Channel object. You can access the channel name and any other custom field you have defined for ths channel
sender object Sender object. You can access the user name or any other custom field you have defined for the user
message object Message object. You can acess the text of the message (or a preview of it if the message is too large) or any other custom field you have defined for the message
unread_count integer Number of unread messages

Here is an example on how to use variable interpolation:

{
    "message": {
        "notification": {
            "title": "New messages",
            "body": "You have {{ unread_count }} new message(s) from {{ sender.name }}"
        },
        "android": {
            "ttl": "86400s",
            "notification": {
                "click_action": "OPEN_ACTIVITY_1"
            }
        }
    }
}

The message that will be sent to firebase will look like this:

{
    "message": {
        "notification": {
            "title": "New messages",
            "body": "You have 3 new message(s) from Tom"
        },
        "android": {
            "ttl": "86400s",
            "notification": {
                "click_action": "OPEN_ACTIVITY_1"
            }
        }
    }
}

Importing data

If you've just started using Stream, you might want to import data from your infrastructure or provider. Instead of using the APIs and creating your own import scripts, you can make use of our import feature.

The process

The steps for importing data into your app are as follows:

  1. Generate the import file for your data (full file reference below)
  2. Reach out to send us the import file
  3. The file will be validated according to the rules described in this doc
  4. If validation passes, a member of our team will approve and run the import
  5. Once the import is completed (usually a few minutes), you will get a confirmation email

Import file

The import file must be structured as a JSON array of objects. Each object is an item to add to your application (eg. a user, a message, ...).

Import entries format

Name Type Description
type string The type of data for this entry. Allowed values are: user, channel, message, reaction, member
item object The data for his entry, see below for the format of each type

Item types

An import file can contain entries with any of these types.

user type
Name Type Description Default Optional
id string the unique id for the user
role string the role of the user user
created_at string the creation time of the user in RFC3339
* string/list/object/point Add as many custom fields as needed
{
  "type": "user",
  "item": {
    "id": "aad24491-c286-4169-bbfc-9280de419cb6",
    "name": "Jesse",
    "profile_image": "http://getstream.com",
    "created_at": "2017-01-01T01:00:00Z",
    "description": "Taj Mahal guitar player at some point"
  }
}
channel type
Name Type Description Default Optional
id string the unique id for the channel
type string the type of channel, eg. livestream
created_by string the id of the user that created the message
frozen boolean channel frozen state false
* string/list/object/point Add as many custom fields as needed
{
  "type": "channel",
  "item": {
    "id": "6e693c74-262d-4d3d-8846-686364c571c8",
    "type": "livestream",
    "created_by": "aad24491-c286-4169-bbfc-9280de419cb6",
    "name": "Rock'n Roll Circus"
  }
}
message type
Name Type Description Default Optional
id string The id of the message A random UUIDv4
channel_type string the type of channel for this message
channel_id string the id of the channel for this message
type string the type of message: regular, deleted or system regular
user string the id of the user that posted the message
attachments array of objects list of message attachments, see attachment type section below []
parent_id string the id of the parent message (if the message is a reply) null
created_at string message creation time in RFC3339 format
updated_at string last time the message was updated created_at
deleted_at string message deletion time in RFC3339 format null
show_in_channel bool reply messages are only shown inside the message thread unless show_in_channel is set to true, in that case the message is shown in the channel as well false
* string/list/object/point Add as many custom fields as needed
message attachments

Messages can contain a list of attachments such as videos, images and other custom types.

Name Type Description Optional
type string the type of attachment. Eg. text, audio, video, image
fallback string
color string
pretext string
author_name string
author_link string
author_icon string
title string
title_link string
text string
image_url string
thumb_url string
footer string
footer_icon string
asset_url string
* string/list/object/point Add as many custom fields as needed
{
  "type": "message",
  "item": {
    "id": "977e691a-c091-4e3b-8f70-ba8944a3e500",
    "channel_type": "livestream",
    "channel_id": "6e693c74-262d-4d3d-8846-686364c571c8",
    "user":"aad24491-c286-4169-bbfc-9280de419cb6",
    "text": "Such a great song, check out my solo at 2:25",
    "type": "regular",
    "created_at": "2017-02-01T02:00:00Z",
    "attachments": [
      {
        "type": "video",
        "url": "https://www.youtube.com/watch?v=flgUbBtjl9c"
      }
    ]
  }
}
reaction type
Name Type Description Optional
message_id string the ID of the message
type string the type of reaction
user_id string the ID of the user
created_at string the creation time of the reaction in RFC3339
* string/list/object/point Add as many custom fields as needed
{
  "type": "reaction",
  "item": {
    "message_id": "977e691a-c091-4e3b-8f70-ba8944a3e500",
    "type": "love",
    "user_id": "aad24491-c286-4169-bbfc-9280de419cb6",
    "created_at": "2019-03-02T15:00:00Z"
  }
}
member type
Name Type Description Default Optional
channel_type string the type of channel for this message
channel_id string the id of the channel
user_id string the user ID
is_moderator boolean true if the user is a channel moderator false
created_at string the membership creation time in RFC3339 current time
{
  "type": "member",
  "item": {
    "channel_type": "livestream",
    "channel_id": "6e693c74-262d-4d3d-8846-686364c571c8",
    "user_id": "aad24491-c286-4169-bbfc-9280de419cb6",
    "is_moderator": true,
    "created_at": "2017-02-01T02:00:00Z"
  }
}

Migrating from Layer

Migrating from Layer is easy. This guide will help you complete the migration quickly:

  1. Export

    The first step is downloading an export of your Layer data. The Layer docs specify how to create an export. Next email support@getstream.io your data export to have it imported to Stream. This process typically takes 1 business day.

  2. Frontend components

    Layer doesn’t provide frontend components. You will want to decide if you want to customize one of Stream’s frontend components or work with the chat API from your own frontend. Implementing a fully featured Chat in React can be very time-consuming.

    Have a look at these 5 examples and see if you can customize them. Swapping the front end components is the fastest way to integrate with Stream.

  3. Differences

    • Stream provides 5 built-in chat types for the most common use cases. The commerce chat type is the most similar to Layer. So you’ll want to use that one as a starting point.
    • Layer has the concept of Distinct Conversations and Non Distinct conversations. With Stream you initialize a channel with a channel type and channel id. So you simply need to make sure the ID is unique when you want to create a new conversation.
    • Stream doesn’t have MessageParts we have Message Attachments. The docs for sending messages are here
    • Conversations are called Channels and instead of Participants Stream has Members.
    • Layer allows you to specify a Metadata field. Stream allows you to add custom data directly to users, channels, messages, attachments and events.
  4. Go live

    After testing your awesome new chat solution discuss a go-live date with Stream’s support team. We’ll re-import your data backup.

  5. Help

    If you have more tips for migrating from Layer to Stream be sure to contact us. We’re refining this guide continuously.

Migrating from Sendbird

Channel types

Sendbird has 2 channel types. The open channel or the group channel. Stream has 5 built-in channel types: livestream, messaging, commerce, gaming, and team. You can also create your own channel types. This allows you to configure the features and permissions to exactly fit your use case. Usually, you’ll want to use the “livestream” chat type if you’re using a Sendbird open channel.

Channels

Instead of the getChannel and channel.enter, Stream uses 1 API call to both get and enter a channel.

The concept of UserMessage and FileMessage in Sendbird is replaced using a Message with a list of attachments.

Thumbnails

Instead of specifying thumbnail sizes upfront you can request different image sizes at read time.

Private vs. Public groups

This difference is handled by Stream’s permission system. You can allow or not allow non-members to edit the list of members for a channel.

Limitations

  • Stream currently doesn’t directly support translating messages. You can write a custom bot to support this though.
  • You can query users