You can listen to events using webhooks, SQS or SNS.
When setting up a webhook you can specify the exact events you want to receive, or select to receive all events.
To ensure that a webhook is triggered by Stream you can verify it's signature.
Webhook retries are in place. If you want to ensure an outage in your API never loses an event, it's better to use SQS or SNS for reliability.
Use an empty event_types array to receive all existing and future events:
// Subscribe to all events (empty array = all events)await client.updateAppSettings({ event_hooks: [ { enabled: true, hook_type: "webhook", webhook_url: "https://example.com/webhooks/stream/all", event_types: [], // empty array = all events }, ],});
from getstream.models import EventHook# Subscribe to all events (empty list = all events)client.update_app( event_hooks=[ EventHook( enabled=True, hook_type="webhook", webhook_url="https://example.com/webhooks/stream/all", event_types=[], # empty list = all events ) ])
require 'getstream_ruby'Models = GetStream::Generated::Models# Subscribe to all events (empty array = all events)client.common.update_app(Models::UpdateAppRequest.new( event_hooks: [ { 'enabled' => true, 'hook_type' => 'webhook', 'webhook_url' => 'https://example.com/webhooks/stream/all', 'event_types' => [] # empty array = all events } ]))
// Subscribe to all events (empty array = all events)$client->updateApp(new Models\UpdateAppRequest( eventHooks: [ new Models\EventHook( enabled: true, hookType: "webhook", webhookUrl: "https://example.com/webhooks/stream/all", eventTypes: [], // empty array = all events ), ],));
// Subscribe to all events (empty slice = all events)client.UpdateApp(ctx, &getstream.UpdateAppRequest{ EventHooks: []getstream.EventHook{ { HookType: getstream.PtrTo("webhook"), Enabled: getstream.PtrTo(true), EventTypes: []string{}, // empty slice = all events WebhookUrl: getstream.PtrTo("https://example.com/webhooks/stream/all"), }, },})
// Subscribe to all events (empty list = all events)var webhookHook = EventHook.builder() .hookType("webhook") .enabled(true) .eventTypes(Collections.emptyList()) // empty list = all events .webhookUrl("https://example.com/webhooks/stream/all") .build();client.updateApp(UpdateAppRequest.builder() .eventHooks(List.of(webhookHook)) .build()).execute();
// Subscribe to all events (empty list = all events)var webhookHook = new EventHook{ HookType = "webhook", Enabled = true, EventTypes = new List<string>(), // empty list = all events WebhookUrl = "https://example.com/webhooks/stream/all",};await client.UpdateAppAsync(new UpdateAppRequest{ EventHooks = new List<EventHook> { webhookHook },});
For reliable event delivery, you can also configure SQS or SNS instead of webhooks.
Webhooks should accept HTTP POST requests with JSON payloads
Response code should be 2xx
Webhook should be ready to accept the same call multiple times: in case of network or remote server failure Stream Chat could retry the request
It's important to validate the signature, so you know the request originated from Stream
Support HTTP Keep-Alive
Use HTTPS
The example below shows how to log the message new and verify the request
// first argument is the request body as a string, second the signature headerconst valid = client.verifyWebhook(req.rawBody, req.headers["x-signature"]);
require 'getstream_ruby'require 'openssl'api_secret = 'STREAM_SECRET'# signature comes from the HTTP header x-signatureexpected = OpenSSL::HMAC.hexdigest('SHA256', api_secret, request_body)valid = expected == signature
$client = new \GetStream\ChatClient("STREAM_API_KEY", "STREAM_API_SECRET");// signature comes from the HTTP header x-signature$jwtGenerator = $client->getJWTGenerator();$valid = $jwtGenerator->verifyWebhookSignature($requestBody, $signature);
client, _ := getstream.NewClient("{{ api_key }}", "{{ api_secret }}")// signature comes from the HTTP header x-signatureisValid := getstream.VerifyWebhookSignature(body, signature, apiSecret)
// signature comes from the HTTP header x-signatureusing System.Security.Cryptography;using System.Text;var encoding = new UTF8Encoding();using var hmac = new HMACSHA256(encoding.GetBytes(apiSecret));var hash = hmac.ComputeHash(encoding.GetBytes(requestBody));var computedSignature = BitConverter.ToString(hash).Replace("-", "").ToLower();var isValid = computedSignature == signature;
// signature comes from the HTTP header x-signaturevar valid = Webhook.verifySignature(body, signature, apiSecret);
All webhook requests contain these headers:
Name
Description
Example
X-Webhook-Id
Unique ID of the webhook call. This value is consistent between retries and could be used to deduplicate retry calls
123e4567-e89b-12d3-a456-426614174000
X-Webhook-Attempt
Number of webhook request attempt starting from 1
1
X-Api-Key
Your application’s API key. Should be used to validate request signature
a1b23cdefgh4
X-Signature
HMAC signature of the request body. See Signature section
The example below shows how to use the push webhooks
// Note: Any previously existing hooks not included in event_hooks array will be deleted.// Get current settings first to preserve your existing configuration.// STEP 1: Get current app settings to preserve existing hooksconst response = await client.getAppSettings();console.log("Current event hooks:", response.event_hooks);// STEP 2: Add webhook hook while preserving existing hooksconst existingHooks = response.event_hooks || [];const newWebhookHook = { enabled: true, hook_type: "webhook", webhook_url: "https://example.com/webhooks/stream/push", event_types: [], // empty array = all events};// STEP 3: Update with complete array including existing hooksawait client.updateAppSettings({ event_hooks: [...existingHooks, newWebhookHook],});// Test the webhook connectionawait client.testWebhookSettings({ webhook_url: "https://example.com/webhooks/stream/push",});
from getstream.models import EventHook# Note: Any previously existing hooks not included in event_hooks array will be deleted.# Get current settings first to preserve your existing configuration.# STEP 1: Get current app settings to preserve existing hooksresponse = client.get_app()existing_hooks = response.data.app.event_hooks or []print("Current event hooks:", existing_hooks)# STEP 2: Add webhook hook while preserving existing hooksnew_webhook_hook = EventHook( enabled=True, hook_type="webhook", webhook_url="https://example.com/webhooks/stream/push", event_types=[], # empty array = all events)# STEP 3: Update with complete array including existing hooksclient.update_app( event_hooks=existing_hooks + [new_webhook_hook])# Test webhook delivery using the Stream Dashboard
require 'getstream_ruby'Models = GetStream::Generated::Models# Note: Any previously existing hooks not included in event_hooks array will be deleted.# Get current settings first to preserve your existing configuration.# STEP 1: Get current app settings to preserve existing hooksresponse = client.common.get_appexisting_hooks = response.app.event_hooks || []puts "Current event hooks:", existing_hooks# STEP 2: Add webhook hook while preserving existing hooksnew_webhook_hook = { 'enabled' => true, 'hook_type' => 'webhook', 'webhook_url' => 'https://example.com/webhooks/stream/push', 'event_types' => [] # empty array = all events}# STEP 3: Update with complete array including existing hooksclient.common.update_app(Models::UpdateAppRequest.new( event_hooks: existing_hooks + [new_webhook_hook]))# Test the webhook connectionclient.common.check_push(Models::CheckPushRequest.new)
// Note: Any previously existing hooks not included in event_hooks array will be deleted.// Get current settings first to preserve your existing configuration.// STEP 1: Get current app settings to preserve existing hooks$response = $client->getApp();$existingHooks = $response->getData()->app->eventHooks ?? [];// STEP 2: Add webhook hook while preserving existing hooks$newWebhookHook = new Models\EventHook( enabled: true, hookType: "webhook", webhookUrl: "https://example.com/webhooks/stream/push", eventTypes: [], // empty array = all events);// STEP 3: Update with complete array including existing hooks$client->updateApp(new Models\UpdateAppRequest( eventHooks: array_merge($existingHooks, [$newWebhookHook]),));
// Note: Any previously existing hooks not included in event_hooks array will be deleted.// Get current settings first to preserve your existing configuration.// STEP 1: Get current app settings to preserve existing hookssettings, err := client.GetApp(ctx, &getstream.GetAppRequest{})if err != nil { log.Fatal(err)}existingHooks := settings.Data.App.EventHooks// STEP 2: Add webhook hook while preserving existing hooksnewWebhookHook := getstream.EventHook{ HookType: getstream.PtrTo("webhook"), Enabled: getstream.PtrTo(true), EventTypes: []string{}, // empty slice = all events WebhookUrl: getstream.PtrTo("https://example.com/webhooks/stream/push"),}// STEP 3: Update with complete array including existing hooksallHooks := append(existingHooks, newWebhookHook)_, err = client.UpdateApp(ctx, &getstream.UpdateAppRequest{ EventHooks: allHooks,})if err != nil { log.Fatal(err)}// Test the webhook connectionclient.CheckPush(ctx, &getstream.CheckPushRequest{})
// Note: Any previously existing hooks not included in event_hooks array will be deleted.// Get current settings first to preserve your existing configuration.// STEP 1: Get current app settings to preserve existing hooksvar response = client.getApp(GetAppRequest.builder().build()).execute().getData();var existingHooks = response.getApp().getEventHooks();System.out.println("Current event hooks: " + existingHooks);// STEP 2: Add webhook hook while preserving existing hooksvar newWebhookHook = EventHook.builder() .hookType("webhook") .enabled(true) .eventTypes(Collections.emptyList()) // empty list = all events .webhookUrl("https://example.com/webhooks/stream/push") .build();// STEP 3: Update with complete array including existing hooksvar allHooks = new ArrayList<>(existingHooks);allHooks.add(newWebhookHook);client.updateApp(UpdateAppRequest.builder() .eventHooks(allHooks) .build()).execute();// Test the webhook connectionclient.checkPush(CheckPushRequest.builder() .build()).execute();
// Note: Any previously existing hooks not included in event_hooks array will be deleted.// Get current settings first to preserve your existing configuration.// STEP 1: Get current app settings to preserve existing hooksvar settings = await client.GetAppAsync();var existingHooks = settings.App.EventHooks ?? new List<EventHook>();Console.WriteLine($"Current event hooks: {existingHooks}");// STEP 2: Add webhook hook while preserving existing hooksvar newWebhookHook = new EventHook{ HookType = "webhook", Enabled = true, EventTypes = new List<string>(), // empty list = all events WebhookUrl = "https://example.com/webhooks/stream/push",};// STEP 3: Update with complete array including existing hooksvar allHooks = new List<EventHook>(existingHooks) { newWebhookHook };await client.UpdateAppAsync(new UpdateAppRequest{ EventHooks = allHooks,});// Test the webhook connectionawait client.CheckPushAsync(new CheckPushRequest{ WebhookUrl = "https://example.com/webhooks/stream/push",});
You can also configure specific event types by providing an array of event names instead of an empty array:
// Configure webhook for specific events onlyconst newWebhookHook = { enabled: true, hook_type: "webhook", webhook_url: "https://example.com/webhooks/stream/messages", event_types: ["message.new", "message.updated", "message.deleted"], // specific events};
from getstream.models import EventHook# Configure webhook for specific events onlynew_webhook_hook = EventHook( enabled=True, hook_type="webhook", webhook_url="https://example.com/webhooks/stream/messages", event_types=["message.new", "message.updated", "message.deleted"], # specific events)
# Configure webhook for specific events onlynew_webhook_hook = { 'enabled' => true, 'hook_type' => 'webhook', 'webhook_url' => 'https://example.com/webhooks/stream/messages', 'event_types' => ['message.new', 'message.updated', 'message.deleted'] # specific events}
// Configure webhook for specific events only$newWebhookHook = new Models\EventHook( enabled: true, hookType: "webhook", webhookUrl: "https://example.com/webhooks/stream/messages", eventTypes: ["message.new", "message.updated", "message.deleted"], // specific events);
// Configure webhook for specific events onlynewWebhookHook := getstream.EventHook{ HookType: getstream.PtrTo("webhook"), Enabled: getstream.PtrTo(true), EventTypes: []string{"message.new", "message.updated", "message.deleted"}, // specific events WebhookUrl: getstream.PtrTo("https://example.com/webhooks/stream/messages"),}
// Configure webhook for specific events onlyvar newWebhookHook = EventHook.builder() .hookType("webhook") .enabled(true) .eventTypes(List.of("message.new", "message.updated", "message.deleted")) // specific events .webhookUrl("https://example.com/webhooks/stream/messages") .build();
// Configure webhook for specific events onlyvar newWebhookHook = new EventHook{ HookType = "webhook", Enabled = true, EventTypes = new List<string> { "message.new", "message.updated", "message.deleted" }, // specific events WebhookUrl = "https://example.com/webhooks/stream/messages",};
Some webhooks contain a field request_info , which holds information about the client that issued the request. This info is intended as an additional signal that you can use for moderation, fraud detection, or other similar purposes.
When configuring the SDK, you may also set an additional x-stream-ext header to be sent with each request. The value of this header is passed along as an ext field in the request_info . You can use this to pass along information that may be useful, such as device information. Refer to the SDK-specific docs on how to set this header.
"request_info": { "type": "client", "ip": "86.84.2.2", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0", "sdk": "stream-chat-react-10.11.0-stream-chat-javascript-client-browser-8.12.1", "ext": "device-id=123"}
For example, in Javascript, you can set the value like this:
The format of the ext header is up to you and you may leave it blank if you don't need it. The value is passed as-is, so you can use a simple value, comma-separated key-values, or more structured data, such as JSON. Binary data must be encoded as a string, for example using base64 or hex encoding.
You may set up to two pending message hooks per application. Only the first commit to a pending message will succeed; any subsequent commit attempts will return an error, as the message is no longer pending. If multiple hooks specify a timeout_ms, the system will use the longest timeout value.
For more information on configuring pending messages, please refer to the Pending Messages documentation.
If necessary, you can only expose your webhook service to Stream. This is possible by configuring your network (eg. iptables rules) to drop all incoming traffic that is not coming from our API infrastructure.
Below you can find the complete list of egress IP addresses that our webhook infrastructure uses. Such list is static and is not changing over time.