# gem install getstream-ruby
require 'getstream_ruby'
require 'jwt'
# instantiate your stream client using the API key and secret
# the secret is only used server side and gives you full access to the API
client = GetStreamRuby.manual(api_key: '{{ api_key }}', api_secret: '{{ api_secret }}')
token = JWT.encode({ user_id: 'john' }, '{{ api_secret }}', 'HS256')
# next, hand this token to the client in your in your login or registration responseBackend
For the average Stream integration, the development work focuses on code that executes in the client. The React, React Native, Swift, Kotlin or Flutter SDKs connect to the chat API directly from the client. However, some tasks must be executed from the server for safety.
The chat API has some features that client side code canmanage in specific cases but usually shouldn't. While these features can be initiated with client side code. We recommend managing them server side instead unless you are certain that you need to manage them from the client side for your specific use case.
The backend has full access to the chat API.
This quick start covers the basics of a backend integration. If we don't have an SDK for your favorite language be sure to review the REST documentation.
Generating user tokens
The backend creates a token for a user. You hand that token to the client side during login or registration. This token allows the client side to connect to the chat API for that user. Stream's permission system does the heavy work of determining which actions are valid for the user, so the backend just needs enough logic to provide a token to give the client side access to a specific user.
The following code shows how to instantiate a client and create a token for a user on the server:
// yarn add stream-chat
import { StreamChat } from "stream-chat";
// if you're using common js
const StreamChat = require("stream-chat").StreamChat;
// instantiate your stream client using the API key and secret
// the secret is only used server side and gives you full access to the API
const serverClient = StreamChat.getInstance(
"{{ api_key }}",
"{{ api_secret }}",
);
// you can still use new StreamChat('api_key', 'api_secret');
// generate a token for the user with id 'john'
const token = serverClient.createToken("john");
// next, hand this token to the client in your in your login or registration response# pip install getstream
from getstream import Stream
# instantiate your stream client using the API key and secret
# the secret is only used server side and gives you full access to the API
server_client = Stream(api_key="{{ api_key }}", api_secret="{{ api_secret }}")
token = server_client.create_token("john")
# next, hand this token to the client in your in your login or registration responseuse GetStream\ChatClient;
// instantiate your stream client using the API key and secret
// the secret is only used server side and gives you full access to the API
$client = new ChatClient("{{ api_key }}", "{{ api_secret }}");
$token = $client->createUserToken("john");
// next, hand this token to the client in your in your login or registration response// go get github.com/GetStream/getstream-go/v4
import "github.com/GetStream/getstream-go/v4"
// instantiate your stream client using the API key and secret
// the secret is only used server side and gives you full access to the API
client, err := getstream.NewClient("{{ api_key }}", "{{ api_secret }}")
token, err := client.CreateToken("john")
// next, hand this token to the client in your in your login or registration response// dotnet add package getstream-net
using GetStream;
// Instantiate your Stream client using the API key and secret
// the secret is only used server side and gives you full access to the API.
var client = new StreamClient("{{ api_key }}", "{{ api_secret }}");
var token = client.CreateUserToken("john");
// Next, hand this token to the client in your in your login or registration response.var client = new StreamSDKClient("YOUR_API_KEY", "YOUR_API_SECRET");
var token = client.tokenBuilder().createToken("john");
// Or with specific expiration (in seconds):
var token = client.tokenBuilder().createToken("john", 3600);You can also generate tokens that expire after a certain time. The tokens & authentication section explains this in detail.
Syncing users
When a user starts a chat conversation with another user both users need to be present in Stream's user storage. So you'll want to make sure that users are synced in advance. The update users endpoint allows you to update 100 users at once, an example is shown below:
Models = GetStream::Generated::Models
client.common.update_users(
Models::UpdateUsersRequest.new(
users: { user_id => Models::UserRequest.new(id: user_id, role: 'admin', custom: { 'mycustomfield' => '123' }) }
)
)const response = await client.upsertUsers([
{
id: userID,
role: "admin",
mycustomfield: "123",
},
]);from getstream.models import UserRequest
server_client.upsert_users(UserRequest(id=user_id, role="admin", custom={"mycustomfield": "123"}))use GetStream\GeneratedModels as Models;
$client->updateUsers(new Models\UpdateUsersRequest(
users: ["bob-1" => new Models\UserRequest(
id: "bob-1",
role: "admin",
custom: (object)["mycustomfield" => "123"],
)],
));client.UpdateUsers(ctx, &getstream.UpdateUsersRequest{
Users: map[string]getstream.UserRequest{
userID: {ID: userID, Role: getstream.PtrTo("admin"), Custom: map[string]any{"mycustomfield": "123"}},
},
})using GetStream.Models;
await client.UpdateUsersAsync(new UpdateUsersRequest
{
Users = new Dictionary<string, UserRequest>
{
["bob-1"] = new UserRequest
{
ID = "bob-1",
Name = "Bob",
Role = "admin",
Custom = new Dictionary<string, object>
{
["mycustomfield"] = "123"
}
}
}
});curl --location --request POST 'https://chat.stream-io-api.com/users?api_key={api_key}' \
--header 'Accept: application/json' \
--header 'Stream-Auth-Type: jwt' \
--header 'Authorization: {{ YOUR TOKEN HERE }}' \
--header 'Content-Type: application/json' \
--data-raw '{
"users": { "Lakshmi": { "id" : "Lakshmi", "name": "Seethalakshmi" }}
}'Chat chat = client.chat();
client.updateUsers(UpdateUsersRequest.builder()
.users(Map.of("bob-1", UserRequest.builder().id("bob-1").name("Bob").build()))
.build()).execute();Note that user roles can only be changed server side. The role you assign to a user impacts their permissions and which actions they can take on a channel.
Syncing Channels
You can create channels client side, but for many applications you'll want to restrict the creation of channels to the backend. Especially if a chat is related to a certain object in your database. One example is building a livestream chat like Twitch. You'll want to create a channel for each Stream and set the channel creator to the owner of the Stream. The example below shows how to create a channel and update it:
Models = GetStream::Generated::Models
client.chat.get_or_create_channel('messaging', 'bob-and-jane',
Models::ChannelGetOrCreateRequest.new(
data: Models::ChannelInput.new(created_by_id: user_id)
)
)
client.chat.update_channel('messaging', 'bob-and-jane',
Models::UpdateChannelRequest.new(
data: Models::ChannelInputRequest.new(
custom: { 'name' => 'my channel name', 'image' => 'my image url', 'mycustomfield' => '123' }
)
)
)const channel = client.channel(type, id, {
created_by_id: "4645",
});
await channel.create();
// create the channel and set created_by to user id 4645
const update = await channel.update({
name: "myspecialchannel",
image: "imageurl",
mycustomfield: "123",
});from getstream.models import ChannelInput, ChannelInputRequest
channel = server_client.chat.channel("messaging", "kung-fu")
channel.get_or_create(data=ChannelInput(created_by_id=user_id))
channel.update(data=ChannelInputRequest(custom={"name": "my channel", "image": "image url", "mycustomfield": "123"}))$client->getOrCreateChannel("messaging", "bob-and-jane", new Models\ChannelGetOrCreateRequest(
data: new Models\ChannelInput(
createdByID: "bob-1",
custom: (object)["name" => "myspecialchannel", "color" => "green"],
),
));channel := client.Chat().Channel("messaging", "123")
channel.GetOrCreate(ctx, &getstream.GetOrCreateChannelRequest{
Data: &getstream.ChannelInput{
CreatedByID: getstream.PtrTo(userID),
Custom: map[string]any{"name": "my channel name", "image": "img url", "mycustomfield": "123"},
},
})using GetStream.Models;
var chat = new ChatClient(client);
var channelResp = await chat.GetOrCreateChannelAsync("messaging", "my-team-channel",
new ChannelGetOrCreateRequest
{
Data = new ChannelInput
{
CreatedByID = "john",
Custom = new Dictionary<string, object>
{
["mycustomfield"] = "123"
}
}
});Channel Creation
curl --location --request POST 'https://chat-proxy-singapore.stream-io-api.com/channels/{channel_type}/{cid}/query?api_key={api_key}' \
--header 'Stream-Auth-Type: jwt' \
--header 'Authorization: {{ YOUR TOKEN HERE }}' \
--header 'Content-Type: application/json' \
--data-raw '{
"data": {
"created_by_id" : "Lakshmi",
"members" : [ { "user_id" : "Bree" }, { "user_id" : "Chree"} ]
}
}'
Update Channel
curl --location --request POST 'https://chat-proxy-singapore.stream-io-api.com/channels/{channel_type}/{cid}?api_key=(api_key}' \
--header 'Stream-Auth-Type: jwt' \--header 'Authorization: {{ YOUR TOKEN HERE }}' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Group channel",
"color": "green",
"message": { "user" : { "id" : "Lakshmi" },
"text" : "Channel color changed by Lakshmi"
}}'chat.channel("messaging", "my-team-channel")
.getOrCreate(GetOrCreateChannelRequest.builder()
.data(ChannelInput.builder()
.createdByID("gandalf-1")
.build())
.build());You can also add a message on the channel when updating it. It's quite common for chat apps to show messages like: "Jack invited John to the channel", or "Kate changed the color of the channel to green".
Adding Members or Moderators
The backend SDKs also make it easy to add or remove members from a channel. The example below shows how to add and remove members:
Models = GetStream::Generated::Models
client.chat.update_channel('<channel-type>', '<channel-id>',
Models::UpdateChannelRequest.new(
add_members: [
Models::ChannelMemberRequest.new(user_id: 'thierry'),
Models::ChannelMemberRequest.new(user_id: 'josh'),
]
)
)
client.chat.update_channel('<channel-type>', '<channel-id>',
Models::UpdateChannelRequest.new(remove_members: ['tommaso'])
)
client.chat.update_channel('<channel-type>', '<channel-id>',
Models::UpdateChannelRequest.new(add_moderators: ['thierry'])
)
client.chat.update_channel('<channel-type>', '<channel-id>',
Models::UpdateChannelRequest.new(demote_moderators: ['thierry'])
)await channel.addMembers(["thierry", "josh"]);
await channel.removeMembers(["tommaso"]);
await channel.addModerators(["thierry"]);
await channel.demoteModerators(["thierry"]);from getstream.models import ChannelMemberRequest
channel.update(add_members=[ChannelMemberRequest(user_id=uid) for uid in ["thierry", "josh", "tommaso"]])
channel.update(remove_members=["tommaso"])
channel.update(add_moderators=["thierry"])
channel.update(demote_moderators=["thierry"])$client->updateChannel("messaging", "bob-and-jane", new Models\UpdateChannelRequest(
addMembers: [
new Models\ChannelMemberRequest(userID: "thierry"),
new Models\ChannelMemberRequest(userID: "josh"),
],
));
$client->updateChannel("messaging", "bob-and-jane", new Models\UpdateChannelRequest(
removeMembers: ["tommaso"],
));
$client->updateChannel("messaging", "bob-and-jane", new Models\UpdateChannelRequest(
addModerators: ["thierry"],
));
$client->updateChannel("messaging", "bob-and-jane", new Models\UpdateChannelRequest(
demoteModerators: ["thierry"],
));channel.Update(ctx, &getstream.UpdateChannelRequest{
AddMembers: []getstream.ChannelMemberRequest{
{UserID: "thierry"},
{UserID: "josh"},
},
})
channel.Update(ctx, &getstream.UpdateChannelRequest{
RemoveMembers: []string{"tommaso"},
})
channel.Update(ctx, &getstream.UpdateChannelRequest{
AddModerators: []string{"thierry"},
})
channel.Update(ctx, &getstream.UpdateChannelRequest{
DemoteModerators: []string{"thierry"},
})using GetStream.Models;
var chat = new ChatClient(client);
await chat.UpdateChannelAsync("<channel-type>", "<channel-id>",
new UpdateChannelRequest
{
AddMembers = new List<ChannelMemberRequest>
{
new ChannelMemberRequest { UserID = "thierry" },
new ChannelMemberRequest { UserID = "josh" }
}
});
await chat.UpdateChannelAsync("<channel-type>", "<channel-id>",
new UpdateChannelRequest
{
RemoveMembers = new List<string> { "thierry" }
});
await chat.UpdateChannelAsync("<channel-type>", "<channel-id>",
new UpdateChannelRequest
{
AddModerators = new List<string> { "thierry" }
});
await chat.UpdateChannelAsync("<channel-type>", "<channel-id>",
new UpdateChannelRequest
{
DemoteModerators = new List<string> { "thierry" }
});curl --location --request POST 'https://chat-proxy-singapore.stream-io-api.com/channels/{channel_type}/{cid}?api_key={api_key}' \
--header 'Accept: application/json' \
--header 'Stream-Auth-Type: jwt' \
--header 'Authorization: {{ YOUR TOKEN HERE }}' \
--header 'Content-Type: application/json' \
--data-raw '{
"add_members" : ["Seetha", "Viswa"]
}'chat.channel("<channel-type>", "<channel-id>")
.update(UpdateChannelRequest.builder()
.addMembers(List.of(ChannelMemberRequest.builder().userID("john").build()))
.hideHistory(true)
.build());
chat.channel("<channel-type>", "<channel-id>")
.update(UpdateChannelRequest.builder()
.removeMembers(List.of("tommaso"))
.build());
chat.channel("<channel-type>", "<channel-id>")
.update(UpdateChannelRequest.builder()
.addModerators(List.of("thierry", "josh"))
.build());
chat.channel("<channel-type>", "<channel-id>")
.update(UpdateChannelRequest.builder()
.demoteModerators(List.of("tommaso"))
.build());Sending Messages
It's quite common that certain actions in your application trigger a message to be sent. If a new user joins an app some chat apps will notify you that your contact joined the app. The example below shows how to send a message from the backend. It's the same syntax as you use client side, but specifying which user is sending the message is required:
Models = GetStream::Generated::Models
client.chat.send_message('<channel-type>', '<channel-id>',
Models::SendMessageRequest.new(
message: Models::MessageRequest.new(
text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.',
mentioned_users: [josh_id],
user_id: john_id,
attachments: [
Models::Attachment.new(
type: 'image',
asset_url: 'https://bit.ly/2K74TaG',
thumb_url: 'https://bit.ly/2Uumxti',
custom: { 'myCustomField' => 123 }
)
],
custom: { 'anotherCustomField' => 234 }
)
)
)const toBeSent = {
text: "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.",
attachments: [
{
type: "image",
asset_url: "https://bit.ly/2K74TaG",
thumb_url: "https://bit.ly/2Uumxti",
myCustomField: 123,
},
],
mentioned_users: [josh.id],
anotherCustomField: 234,
};
const message = await channel.sendMessage({ ...toBeSent, user_id: "john" });
message = {
text: "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.",
attachments: [
{
type: "image",
asset_url: "https://bit.ly/2K74TaG",
thumb_url: "https://bit.ly/2Uumxti",
myCustomField: 123,
},
],
mentioned_users: [josh["id"]],
anotherCustomField: 234,
};from getstream.models import Attachment, MessageRequest
channel.send_message(
MessageRequest(
text="@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.",
attachments=[
Attachment(
type="image",
asset_url="https://bit.ly/2K74TaG",
thumb_url="https://bit.ly/2Uumxti",
custom={"myCustomField": 123},
)
],
mentioned_users=[josh["id"]],
user_id="thierry",
custom={"anotherCustomField": 234},
)
)$response = $client->sendMessage("messaging", "bob-and-jane", new Models\SendMessageRequest(
message: new Models\MessageRequest(
text: "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.",
userID: "john",
attachments: [
new Models\Attachment(
type: "image",
assetUrl: "https://bit.ly/2K74TaG",
thumbUrl: "https://bit.ly/2Uumxti",
custom: (object)["myCustomField" => 123],
),
],
mentionedUsers: ["josh"],
custom: (object)["anotherCustomField" => 234],
),
));channel.SendMessage(ctx, &getstream.SendMessageRequest{
Message: getstream.MessageRequest{
Text: getstream.PtrTo("@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish."),
MentionedUsers: []string{joshID},
UserID: getstream.PtrTo(johnID),
Custom: map[string]any{"anotherCustomField": 234},
},
})using GetStream.Models;
var chat = new ChatClient(client);
var msgResp = await chat.SendMessageAsync("<channel-type>", "<channel-id>",
new SendMessageRequest
{
Message = new MessageRequest
{
Text = "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.",
MentionedUsers = new List<string> { joshId },
UserID = "john",
Attachments = new List<Attachment>
{
new Attachment
{
Type = "image",
AssetUrl = "https://bit.ly/2K74TaG",
ThumbUrl = "https://bit.ly/2Uumxti",
Custom = new Dictionary<string, object>
{
["myCustomField"] = 123
}
}
},
Custom = new Dictionary<string, object>
{
["anotherCustomField"] = 234
}
}
});curl --location --request POST 'https://chat-proxy-singapore.stream-io-api.com/channels/{channel_type}/{cid}/message?api_key={api_key}' \
--header 'Stream-Auth-Type: jwt' \
--header 'Authorization: {{ YOUR TOKEN HERE }}' \
--header 'Content-Type: application/json' \
--data-raw '{
"message" :
{
"text" : "Hello World",
"user" : { "id" : "Lakshmi" }
}
}'var message = MessageRequest.builder()
.text("@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.")
.mentionedUsers(List.of(josh.getId()))
.userID(userId)
.attachments(List.of(
Attachment.builder()
.type("image")
.build()))
.custom(Map.of("myCustomField", "123"))
.build();
chat.channel("<channel-type>", "<channel-id>")
.sendMessage(SendMessageRequest.builder()
.message(message)
.build());Changing Application Settings
Some application settings should only be changed using the back end for security reasons:
API Changes
A breaking change is a change that may require you to make changes to your application in order to avoid disruption to your integration. Stream will never introduce a breaking change without notifying its customers and giving them plenty of time to make the appropriate changes on their end. The following are a few examples of changes we consider breaking:
Changes to existing permission definitions.
Removal of an allowed parameter, request field or response field.
Addition of a required parameter or request field without default values.
Changes to the intended functionality of an endpoint. For example, if a DELETE request previously used to soft delete the resource but now hard deletes the resource.
Introduction of a new validation.
A non-breaking change is a change that you can adapt to at your own discretion and pace without disruption. Ensure that your application is designed to be able to handle the following types of non-breaking changes without prior notice from Stream:
Addition of new endpoints.
Addition of new methods to existing endpoints.
Addition of new fields in the following scenarios:
New fields in responses.
New optional request fields or parameters.
New required request fields that have default values.
Addition of a new value returned for an existing text field.
Changes to the order of fields returned within a response.
Addition of an optional request header.
Removal of redundant request header.
Changes to the length of data returned within a field.
Changes to the overall response size.
Changes to error messages. We do not recommend parsing error messages to perform business logic. Instead, you should only rely on HTTP response codes and error codes.
Fixes to HTTP response codes and error codes from incorrect code to correct code.
Prefix your custom data properties, we could introduce new features that might clash with custom property names you are using, by prefixing them you avoid this problem.