# SQS

Stream can send payloads of all events from your application to an [Amazon SQS](https://aws.amazon.com/sqs/) queue you own.

A chat application with a lot of users generates a lots of events. With a standard Webhook configuration, events are posted to your server and can overwhelm unprepared servers during high-use periods. While the server is out, it will not be able to receive Webhooks and will fail to process them. One way to avoid this issue is to use Stream Chat's support for sending webhooks to Amazon SQS.

SQS removes the chance of losing data for Chat events by providing a large, scalable bucket that holds events generated by Stream Chat in a queue for your server or other .

The complete list of supported events is identical to those sent through webhooks and can be found on the [Events](/chat/docs/java/webhook-events/) page.

## Configuration

You can configure your SQS queue programmatically using the REST API or an SDK with Server Side Authorization.

There are 2 ways to configure authentication on your SQS queue:

1. By providing a key and secret

2. Or by having Stream's AWS account assume a role on your SQS queue. With this option you omit the key and secret, but instead you set up a resource-based policy to grant Stream SendMessage permission on your SQS queue. The following policy needs to be attached to your queue (replace the value of Resource with the fully qualified ARN of your queue):

<Tabs>

```json label="JSON"
{
  "Sid": "AllowStreamProdAccount",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::185583345998:root"
  },
  "Action": "SQS:SendMessage",
  "Resource": "arn:aws:sqs:us-west-2:1111111111:customer-sqs-for-stream"
}
```

</Tabs>

To configure an SQS queue, use the `event_hooks` array and Update App Settings method:

<Tabs>

```js label="JavaScript"
// 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
const response = await client.getAppSettings();
console.log("Current event hooks:", response.event_hooks);

// STEP 2: Add SQS hook while preserving existing hooks
const existingHooks = response.event_hooks || [];
const newSQSHook = {
  enabled: true,
  hook_type: "sqs",
  sqs_queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
  sqs_region: "us-east-1",
  sqs_auth_type: "keys", // or "resource" for role-based auth
  sqs_key: "yourkey",
  sqs_secret: "yoursecret",
  event_types: [], // empty array = all events
};

// STEP 3: Update with complete array including existing hooks
await client.updateAppSettings({
  event_hooks: [...existingHooks, newSQSHook],
});

// Test the SQS connection
await client.testSQSSettings();
```

```python label="Python"
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 hooks
response = client.get_app()
existing_hooks = response.data.app.event_hooks or []
print("Current event hooks:", existing_hooks)

# STEP 2: Add SQS hook while preserving existing hooks
new_sqs_hook = EventHook(
    enabled=True,
    hook_type="sqs",
    sqs_queue_url="https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
    sqs_region="us-east-1",
    sqs_auth_type="keys",  # or "resource" for role-based auth
    sqs_key="yourkey",
    sqs_secret="yoursecret",
    event_types=[],  # empty array = all events
)

# STEP 3: Update with complete array including existing hooks
client.update_app(
    event_hooks=existing_hooks + [new_sqs_hook]
)

# Test the SQS connection
client.check_sqs(sqs_key="yourkey", sqs_secret="yoursecret", sqs_url="https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue")
```

```ruby label="Ruby"
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 hooks
response = client.common.get_app
existing_hooks = response.app.event_hooks || []
puts "Current event hooks:", existing_hooks

# STEP 2: Add SQS hook while preserving existing hooks
new_sqs_hook = {
  'enabled' => true,
  'hook_type' => 'sqs',
  'sqs_queue_url' => 'https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue',
  'sqs_region' => 'us-east-1',
  'sqs_auth_type' => 'keys', # or "resource" for role-based auth
  'sqs_key' => 'yourkey',
  'sqs_secret' => 'yoursecret',
  'event_types' => [] # empty array = all events
}

# STEP 3: Update with complete array including existing hooks
client.common.update_app(Models::UpdateAppRequest.new(
  event_hooks: existing_hooks + [new_sqs_hook]
))

# Test the SQS connection
client.common.check_sqs(Models::CheckSQSRequest.new(
  sqs_key: 'yourkey',
  sqs_secret: 'yoursecret',
  sqs_url: 'https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue'
))
```

```php label="PHP"
// 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 SQS hook while preserving existing hooks
$newSQSHook = new Models\EventHook(
    enabled: true,
    hookType: "sqs",
    sqsQueueUrl: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
    sqsRegion: "us-east-1",
    sqsAuthType: "keys", // or "resource" for role-based auth
    sqsKey: "yourkey",
    sqsSecret: "yoursecret",
    eventTypes: [], // empty array = all events
);

// STEP 3: Update with complete array including existing hooks
$client->updateApp(new Models\UpdateAppRequest(
    eventHooks: array_merge($existingHooks, [$newSQSHook]),
));

// Test the SQS connection
$client->checkSQS(new Models\CheckSQSRequest(
    sqsUrl: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
    sqsKey: "yourkey",
    sqsSecret: "yoursecret",
));
```

```go label="Go"
// 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
settings, err := client.GetApp(ctx, &getstream.GetAppRequest{})
if err != nil {
    log.Fatal(err)
}
existingHooks := settings.Data.App.EventHooks
fmt.Printf("Current event hooks: %+v\n", existingHooks)

// STEP 2: Add SQS hook while preserving existing hooks
newSQSHook := getstream.EventHook{
    HookType:    getstream.PtrTo("sqs"),
    Enabled:     getstream.PtrTo(true),
    EventTypes:  []string{}, // empty slice = all events
    SqsQueueUrl: getstream.PtrTo("https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"),
    SqsRegion:   getstream.PtrTo("us-east-1"),
    SqsAuthType: getstream.PtrTo("keys"), // or "resource" for role-based auth
    SqsKey:      getstream.PtrTo("yourkey"),
    SqsSecret:   getstream.PtrTo("yoursecret"),
}

// STEP 3: Update with complete array including existing hooks
allHooks := append(existingHooks, newSQSHook)
_, err = client.UpdateApp(ctx, &getstream.UpdateAppRequest{
    EventHooks: allHooks,
})
if err != nil {
    log.Fatal(err)
}

// Test the SQS connection
client.CheckSQS(ctx, &getstream.CheckSQSRequest{
    SqsUrl:    getstream.PtrTo("https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"),
    SqsKey:    getstream.PtrTo("yourkey"),
    SqsSecret: getstream.PtrTo("yoursecret"),
})
```

```java label="Java"
// 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
var response = client.getApp(GetAppRequest.builder().build()).execute().getData();
var existingHooks = response.getApp().getEventHooks();
System.out.println("Current event hooks: " + existingHooks);

// STEP 2: Add SQS hook while preserving existing hooks
var newSQSHook = EventHook.builder()
    .hookType("sqs")
    .enabled(true)
    .eventTypes(Collections.emptyList()) // empty list = all events
    .sqsQueueUrl("https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue")
    .sqsRegion("us-east-1")
    .sqsAuthType("keys") // or "resource" for role-based auth
    .sqsKey("yourkey")
    .sqsSecret("yoursecret")
    .build();

// STEP 3: Update with complete array including existing hooks
var allHooks = new ArrayList<>(existingHooks);
allHooks.add(newSQSHook);
client.updateApp(UpdateAppRequest.builder()
    .eventHooks(allHooks)
    .build()).execute();

// Test the SQS connection
client.checkSQS(CheckSQSRequest.builder()
    .sqsKey("yourkey")
    .sqsSecret("yoursecret")
    .sqsUrl("https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue")
    .build()).execute();
```

```csharp label="C#"
// 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
var settings = await client.GetAppAsync();
var existingHooks = settings.Data.App.EventHooks ?? new List<EventHook>();
Console.WriteLine($"Current event hooks: {existingHooks}");

// STEP 2: Add SQS hook while preserving existing hooks
var newSQSHook = new EventHook
{
    HookType = "sqs",
    Enabled = true,
    EventTypes = new List<string>(), // empty list = all events
    SqsQueueUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
    SqsRegion = "us-east-1",
    SqsAuthType = "keys", // or "resource" for role-based auth
    SqsKey = "yourkey",
    SqsSecret = "yoursecret",
};

// STEP 3: Update with complete array including existing hooks
var allHooks = new List<EventHook>(existingHooks) { newSQSHook };
await client.UpdateAppAsync(new UpdateAppRequest
{
    EventHooks = allHooks,
});

// Test the SQS connection
await client.CheckSQSAsync(new CheckSQSRequest
{
    SqsKey = "yourkey",
    SqsSecret = "yoursecret",
    SqsUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
});
```

</Tabs>

## Configuration Options

The following options are available when configuring an SQS event hook:

| Option        | Type    | Description                                                                             | Required                                                                           |
| ------------- | ------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| id            | string  | Unique identifier for the event hook                                                    | No. If empty, it will generate an ID.                                              |
| enabled       | boolean | Boolean flag to enable/disable the hook                                                 | Yes                                                                                |
| hook_type     | string  | Must be set to `"sqs"`                                                                  | Yes                                                                                |
| sqs_queue_url | string  | The AWS SQS queue URL                                                                   | Yes                                                                                |
| sqs_region    | string  | The AWS region where the SQS queue is located (e.g., "us-east-1")                       | Yes                                                                                |
| sqs_auth_type | string  | Authentication type: `"keys"` for access key/secret or `"resource"` for role-based auth | Yes                                                                                |
| sqs_key       | string  | AWS access key ID (required if auth_type is "keys")                                     | Yes if using key auth                                                              |
| sqs_secret    | string  | AWS secret access key (required if auth_type is "keys")                                 | Yes if using key auth                                                              |
| event_types   | array   | Array of event types this hook should handle                                            | No. Not provided or empty array means subscribe to all existing and future events. |

## SQS Permissions

Stream needs the right permissions on your SQS queue to be able to send events to it. If updates are not showing up in your queue add the following permission policy to the queue:

<Tabs>

```json label="JSON"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1459523779000",
      "Effect": "Allow",
      "Action": [
        "sqs:GetQueueUrl",
        "sqs:SendMessage",
        "sqs:SendMessageBatch",
        "sqs:GetQueueAttributes"
      ],
      "Resource": ["arn:aws:sqs:region:acc_id:queue_name"]
    }
  ]
}
```

</Tabs>

Here's an example list of messages read from your SQS queue:

<disclosure label="Response">

```json
{
  "type": "message.new",
  "cid": "messaging:fun-d5f396e3-fbaf-469c-9b45-8837b4f75baa",
  "message": {
    "id": "8bffc454-e1da-4d91-8b88-a87853dfb41c",
    "text": "Welcome to the Community!",
    "html": "<p>Welcome to the Community!</p>\n",
    "type": "regular",
    "user": {
      "id": "tommaso-52ec3a5f-e916-469f-bf54-b53b5247a4b0",
      "role": "user",
      "created_at": "2020-03-30T07:54:46.207332Z",
      "updated_at": "2020-03-30T07:54:46.207719Z",
      "banned": false,
      "online": false
    },
    "attachments": [],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": null,
    "reaction_scores": {},
    "reply_count": 0,
    "created_at": "2020-03-30T07:54:46.277381Z",
    "updated_at": "2020-03-30T07:54:46.277382Z",
    "mentioned_users": []
  },
  "user": {
    "id": "tommaso-52ec3a5f-e916-469f-bf54-b53b5247a4b0",
    "role": "user",
    "created_at": "2020-03-30T07:54:46.207332Z",
    "updated_at": "2020-03-30T07:54:46.207719Z",
    "banned": false,
    "online": false,
    "channel_unread_count": 0,
    "channel_last_read_at": "2020-03-30T07:54:46.270208768Z",
    "total_unread_count": 0,
    "unread_channels": 0,
    "unread_count": 0
  },
  "created_at": "2020-03-30T07:54:46.295138Z",
  "members": [
    {
      "user_id": "thierry-735d0d44-8bf1-40df-81db-fa83363ac790",
      "user": {
        "id": "tommaso-52ec3a5f-e916-469f-bf54-b53b5247a4b0",
        "role": "user",
        "created_at": "2020-03-30T07:54:46.207332Z",
        "updated_at": "2020-03-30T07:54:46.207719Z",
        "banned": false,
        "online": false
      },
      "created_at": "2020-03-30T07:54:46.255628Z",
      "updated_at": "2020-03-30T07:54:46.255628Z"
    }
  ],
  "channel_type": "messaging",
  "channel_id": "fun-d5f396e3-fbaf-469c-9b45-8837b4f75baa"
}
```

</disclosure>

## Payload Compression

SQS honours the same `enable_hook_payload_compression` flag — see the [webhooks overview](/chat/docs/java/webhooks-overview/#payload-compression) for enablement and the production checklist. When compression is on, the message body is gzipped + base64-encoded (SQS only accepts UTF-8) and the producer sets these message attributes:

```json
{
  "content_encoding": "gzip",
  "content_type": "application/json",
  "payload_encoding": "base64"
}
```

### Reading messages from SQS

Call `parseSqs` on your chat client with the SQS `Body` string — it reverses the base64 + gzip envelope and returns a typed event (with an `UnknownEvent` fallback), the same shape `verifyAndParseWebhook` returns for HTTP. The same call works whether or not compression is on (encoding is detected from the body bytes, so the `content_encoding` / `payload_encoding` attributes are only a hint).

<admonition type="note">

**No `X-Signature` on SQS.** Stream does not ship an HMAC signature on SQS deliveries. The transport is authenticated end-to-end — the queue is gated by IAM, so only your account can read it, and only Stream's account can write to it.

</admonition>

<Tabs>

```js label="JavaScript"
// message is the SQS Message object you received from ReceiveMessageCommand
const event = client.parseSqs(message.Body);
// event.type, event.message, event.user, ...
```

```python label="Python"
# message is an SQS message dict from boto3 receive_message
event = client.parse_sqs(message["Body"])
# event.type, event.message, event.user, ...
```

```ruby label="Ruby"
event = client.parse_sqs(message.body)
```

```php label="PHP"
$event = $client->parseSqs($message['Body']);
```

```go label="Go"
event, err := client.ParseSqs(*message.Body)
```

```csharp label="C#"
var ev = client.ParseSqs(message.Body);
```

```java label="Java"
var event = client.parseSqs(message.body());
```

</Tabs>

#### Where each argument comes from

| Argument | Source                                         | Example        |
| -------- | ---------------------------------------------- | -------------- |
| `body`   | `Body` field of the SQS message (UTF-8 string) | `message.Body` |

`parse_sqs` takes only the message body. No HMAC is involved; use your API secret only for other chat API calls, not for parsing SQS payloads.

<admonition type="note">

**Need a stateless helper?** The same decode + parse logic is exposed as a static / module-level `parse_sqs` (see the [webhooks overview](/chat/docs/java/webhooks-overview/#handling-the-webhook) for per-language imports). Use it in workers that don't keep a chat client around.

</admonition>

Building this without a Stream SDK? Expand the per-language reference implementation below.

<disclosure label="Reference implementation (no SDK)">

SQS adds one primitive on top of the building blocks from the [webhooks overview](/chat/docs/java/webhooks-overview/#handling-the-webhook):

| Helper                              | Purpose                                                                                                                                                                 |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `decode_sqs_payload(body) -> bytes` | Base64-decode the SQS message body, then gzip-decompress when the result starts with the [RFC 1952](https://datatracker.ietf.org/doc/html/rfc1952) gzip magic (`1f 8b`) |
| `parse_sqs(body)`                   | `parse_event(decode_sqs_payload(body))` — reuses `parse_event` from the overview; no `verify_signature` step                                                            |

`parse_event` is identical to the HTTP webhook implementation on the overview — only the body decoder is different. The references below show the composite per language.

<Tabs>

```js label="JavaScript"
const GZIP_MAGIC = Buffer.from([0x1f, 0x8b]);

function decodeSqsPayload(body) {
  let buf = Buffer.from(body, "base64");
  if (buf.length < 2 || !buf.subarray(0, 2).equals(GZIP_MAGIC)) {
    // not base64+gzip — fall back to the raw UTF-8 body (uncompressed case)
    buf = Buffer.from(body, "utf8");
  }
  if (buf.length >= 2 && buf.subarray(0, 2).equals(GZIP_MAGIC)) {
    buf = require("zlib").gunzipSync(buf);
  }
  return buf;
}

function parseSqs(messageBody) {
  return parseEvent(decodeSqsPayload(messageBody));
}

// Usage with @aws-sdk/client-sqs:
const event = parseSqs(message.Body);
```

```python label="Python"
import base64, gzip, json

GZIP_MAGIC = b"\x1f\x8b"

def decode_sqs_payload(body: str) -> bytes:
    try:
        buf = base64.b64decode(body, validate=True)
    except Exception:
        buf = body.encode("utf-8")
    if buf[:2] == GZIP_MAGIC:
        buf = gzip.decompress(buf)
    return buf

def parse_sqs(message_body):
    return parse_event(decode_sqs_payload(message_body))

# Usage with boto3 sqs.receive_message():
event = parse_sqs(message["Body"])
```

```ruby label="Ruby"
require 'base64'
require 'json'
require 'zlib'
require 'stringio'

GZIP_MAGIC = "\x1f\x8b".b

def decode_sqs_payload(body)
  begin
    buf = Base64.strict_decode64(body)
  rescue ArgumentError
    buf = body.b
  end
  buf.start_with?(GZIP_MAGIC) ? Zlib::GzipReader.new(StringIO.new(buf)).read : buf
end

def parse_sqs(message_body)
  parse_event(decode_sqs_payload(message_body))
end

# Usage with the AWS SDK for Ruby:
event = parse_sqs(message.body)
```

```php label="PHP"
function decodeSqsPayload(string $body): string {
    $buf = base64_decode($body, true);
    if ($buf === false) {
        $buf = $body;
    }
    if (substr($buf, 0, 2) === "\x1f\x8b") {
        $buf = gzdecode($buf);
        if ($buf === false) {
            throw new RuntimeException('gzip decode failed');
        }
    }
    return $buf;
}

function parseSqs(string $messageBody): array {
    return parseEvent(decodeSqsPayload($messageBody));
}

# Usage with the AWS SDK for PHP:
$event = parseSqs($message['Body']);
```

```go label="Go"
package webhook

import (
    "bytes"
    "compress/gzip"
    "encoding/base64"
    "io"
)

var gzipMagic = []byte{0x1f, 0x8b}

func DecodeSqsPayload(body string) ([]byte, error) {
    buf, err := base64.StdEncoding.DecodeString(body)
    if err != nil {
        buf = []byte(body)
    }
    if len(buf) >= 2 && bytes.Equal(buf[:2], gzipMagic) {
        gz, err := gzip.NewReader(bytes.NewReader(buf))
        if err != nil {
            return nil, err
        }
        defer gz.Close()
        return io.ReadAll(gz)
    }
    return buf, nil
}

func ParseSqs(messageBody string) (map[string]any, error) {
    payload, err := DecodeSqsPayload(messageBody)
    if err != nil {
        return nil, err
    }
    return ParseEvent(payload)
}

// Usage with aws-sdk-go-v2 sqs.ReceiveMessage:
// event, err := ParseSqs(*message.Body)
```

```java label="Java"
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.zip.GZIPInputStream;

public static byte[] decodeSqsPayload(String body) throws Exception {
    byte[] buf;
    try {
        buf = Base64.getDecoder().decode(body);
    } catch (IllegalArgumentException e) {
        buf = body.getBytes(java.nio.charset.StandardCharsets.UTF_8);
    }
    if (buf.length >= 2 && buf[0] == 0x1f && (buf[1] & 0xff) == 0x8b) {
        try (var gz = new GZIPInputStream(new ByteArrayInputStream(buf));
             var out = new ByteArrayOutputStream()) {
            gz.transferTo(out);
            return out.toByteArray();
        }
    }
    return buf;
}

public static JsonNode parseSqs(String messageBody) throws Exception {
    byte[] payload = decodeSqsPayload(messageBody);
    return parseEvent(payload);
}

// Usage with software.amazon.awssdk.services.sqs:
// var event = parseSqs(message.body());
```

```csharp label="C#"
public static byte[] DecodeSqsPayload(string body)
{
    byte[] buf;
    try { buf = Convert.FromBase64String(body); }
    catch (FormatException) { buf = Encoding.UTF8.GetBytes(body); }

    if (buf.Length >= 2 && buf[0] == 0x1f && buf[1] == 0x8b)
    {
        using var gz = new GZipStream(new MemoryStream(buf), CompressionMode.Decompress);
        using var ms = new MemoryStream();
        gz.CopyTo(ms);
        return ms.ToArray();
    }
    return buf;
}

public static JsonElement ParseSqs(string messageBody)
{
    var payload = DecodeSqsPayload(messageBody);
    return ParseEvent(payload);
}

// Usage with AWSSDK.SQS:
// var ev = ParseSqs(message.Body);
```

</Tabs>

</disclosure>

Enabling compression and the production checklist are documented on the [webhooks overview](/chat/docs/java/webhooks-overview/#enabling-compression) — the same `enable_hook_payload_compression` flag covers SQS.

### SQS Best practices and Assumptions

- Set the maximum message size set to 256 KB.

Messages bigger than the maximum message size will be dropped.

- Set up a dead-letter queue for your main queue.

This queue will hold the messages that couldn't be processed successfully and is useful for debugging your application.


---

This page was last updated at 2026-06-12T08:03:45.875Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/java/sqs/](https://getstream.io/chat/docs/java/sqs/).