# Pending Messages

Pending Messages features lets you introduce asynchronous moderation on messages being sent on channel. To use this feature please get in touch with support so that we can enable it for your organisation.

## Sending Pending Messages

Messages can be made pending by default by setting the channel config property `mark_messages_pending` to true.

<codetabs>

<codetabs-item value="nodejs" label="Node">

```js
response = await client.updateChannelType("messaging", {
  mark_messages_pending: true,
});
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.update_channel_type("messaging", mark_messages_pending=True)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$response = $this->client->updateChannelType("messaging", ["mark_messages_pending" => true]);
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
_, err := client.UpdateChannelType(ctx, "messaging", map[string]interface{}{"mark_messages_pending": true})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# require 'stream-chat'

@client.update_channel_type('messaging', mark_messages_pending: true)
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
ChannelType
  .update("messaging")
  .markMessagesPending(true)
  .request()
```

</codetabs-item>

</codetabs>

You can also set the `pending` property on a message to mark it as pending on server side (this will override the channel configuration). **Please note that this is only server-side feature** .

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const message = await channel.sendMessage(
  {
    text: "this is my pending message",
    user_id: "user-id",
  },
  {
    pending: true,
    pending_message_metadata: {
      my: "metadata",
    },
  },
);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
MessageRequestObject messageRequest =
 MessageRequestObject.builder()
  .text(text)
  .userId(testUserRequestObject.getId())
  .build();
Map<String, Object> metadata = new HashMap<String, Object>();
metadata.put("custom_field", "true");

Message.send(testChannel.getType(), testChannel.getId())
 .message(messageRequest)
 .pending(true)
 .pendingMessageMetadata(metadata)
 .request()
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$msg = ["id" => "unique_id_21312", "text" => "hello world"];
$channel->sendMessage($msg, $userId, null, ["pending" => true]);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
options = {
   pending: true,
   pending_message_metadata: {
    metadata: 'some_data'
   }
  }
  msg = @channel.send_message({ text: 'hi' }, @random_user[:id], **options)
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = channel.send_message(
      {"text": "hi"}, random_user["id"], pending=True, pending_message_metadata={"extra_data": "test"}
    )
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
msg := &Message{Text: "test pending message"}
	metadata := map[string]string{"my": "metadata"}
	messageResp, err := Channel.SendMessage(ctx, msg, user.ID, MessagePending, MessagePendingMessageMetadata(metadata))
```

</codetabs-item>

</codetabs>

Pending messages will only be visible to the user that sent them. They will not be query-able by other users.

## Callbacks

<admonition type="important">

Stream is gradually rolling out a new multi-event hooks system. If you receive an error about configuration, please refer to the [Multi-Event Hooks](/chat/docs/<framework>/multi_event_hooks/) documentation for the new configuration method.

</admonition>

When a pending message is sent or deleted, the message along with its pending message metadata will be forwarded to a callback over HTTP(s). You can configure this callback address by updating the application configuration.

<codetabs>

<codetabs-item value="nodejs" label="Node">

```js
await serverClient.updateAppSettings({
  async_moderation_config: {
    callback: {
      mode: "CALLBACK_MODE_REST",
      server_url: "your callback server url",
    },
    timeout_ms: 10000, // how long messages should stay pending before being deleted
  },
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
App
 .update()
 .asyncModerationConfig(
   App.AsyncModerationConfigRequestObject.builder().callback(
    App.AsyncModerationCallback.builder()
     .mode("CALLBACK_MODE_REST")
     .serverUrl("http://localhost.com")
     .build()
   ).timeoutMs(30000).build()
 )
 .request()
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$appSettings = array(
  'async_moderation_config' => array(
    'callback' => array(
      'mode' => 'CALLBACK_MODE_REST',
      'server_url' => 'http://localhost:8000/moderation_callbacksss',
    ),
    'timeout_ms' => 10000 // how long messages should stay pending before being deleted
  )
);
$response = $this->client->updateAppSettings($appSettings);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
@client.update_app_settings(async_moderation_config: {
   callback: {
    mode: 'CALLBACK_MODE_REST',
    server_url: 'https://example.com/callback'
   },
   timeout_ms => 10000
  })
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.update_app_settings(async_moderation_config={
  'callback': {
    'mode': 'CALLBACK_MODE_REST',
    'server_url': 'http://example.com/callback',
  },
  'timeout_ms': 10000 # how long messages should stay pending before being deleted
})
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
settings := NewAppSettings().
SetAsyncModerationConfig(
  AsyncModerationConfiguration{
    Callback: &AsyncModerationCallback{
      Mode:   "CALLBACK_MODE_REST",
      ServerUrl: "https://example.com/gosdk",
    },
    Timeout: 10000,
  },
)

_, err := client.UpdateAppSettings(ctx, settings)
```

</codetabs-item>

</codetabs>

For example, if your callback server url is <https://example.com>, we would send callbacks:

- When pending message is sent

`POST https://example.com/PassOnPendingMessage`

- When a pending message is deleted

`POST https://https://example.com/DeletedPendingMessage`

In both callbacks, the body of the POST request will be of the form:

<codetabs>

<codetabs-item value="json" label="JSON">

```json
{
  "message": {
    // the message object
  },
  "metadata": {
    // keys and values that you passed as pending_message_metadata
  },
  "request_info": {
    // request info of the request that sent the pending message. Example:
    /*
    "type": "client",
    "ip": "127.0.0.1",
    "user_agent": "Mozilla/5.0...",
    "sdk": "stream-chat-js",
    "ext": "additional-data"
    */
  }
}
```

</codetabs-item>

</codetabs>

## Deleting pending messages

Pending messages can be deleted using the normal delete message endpoint. Users are only able to delete their own pending messages. The messages must be hard deleted. Soft deleting a pending message will return an error.

## Updating pending messages

Pending messages cannot be updated.

## Querying pending messages

A user can retrieve their own pending messages using the following endpoints:

<codetabs>

<codetabs-item value="nodejs" label="Node">

```js
// To retrieve single message
const response = await client.getMessage("pending_message_id");
console.log(response.message, response.pending_message_metadata);

// To retrieve multiple messages
const response = await channel.getMessagesById([
  "pending_message_id_1",
  "pending_message_id_2",
]);
console.log(response.messages);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// To query single pending message
Message.get(pendingMessageId).request();

// To query multiple pending messages
Message.getMany(
 "channel_type",
 "channel_id",
 Arrays.asList("pendingMessageId1", "pendingMessageId2")
).request()
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// To retrieve single message
$message = $client->getMessage('message-id');

// To retrieve multiple messages
$msgResponse = $channel->getManyMessages(["message-1", "message-2"]);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# To retrieve single message
message = @client.get_message(msg_id)[:message]

# To retrieve multiple messages
messages = @channel.get_messages(['message-1', 'message-2'])[:messages]
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# To retrieve single message
client.get_message(msg_id)

# To retrieve multiple messages
client.get_messages(['message-1', 'message-2'])
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// To retrieve single message
messageResp, err = client.GetMessage(ctx, "message-id")

// To retrieve multiple messages
getMsgResp, err := channel.GetMessages(ctx, []string{"message-1", "message-2"})
```

</codetabs-item>

</codetabs>

## Query channels

Each channel that is returned from query channels will also have an array of `pending_messages` . These are pending messages that were sent to this channel, and belong to the user who made the query channels call. This array will contain a maximum of 100 messages and these will be the 100 most recently sent messages.

<codetabs>

<codetabs-item value="nodejs" label="Node">

```js
// Quering multiple channels
const channels = await client.queryChannels(filters);
console.log(channels[0].state.pending_messages);

// Querying single channel
const channel = await client.channel("messaging", "channel_id");
await channel.query();
console.log(channel.state.pending_messages);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
List<ChannelGetResponse> channels = Assertions.assertDoesNotThrow(
    () ->
      Channel.list()
        .user(testUserRequestObject)
        .sort(Sort.builder().field("id").direction(Direction.DESC).build())
        .request()).getChannels();

  System.out.println(channels.get(0).getPendingMessages());
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$response = $client->queryChannels(["id" => $this->channel->id], null, ['user_id' => $this->user1["id"]]);
print_r($response["channels"][0]['pending_messages']);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
response = @client.query_channels({ 'members' => { '$in' => ['legolas'] } }, sort: { 'id' => 1 })

# Access pending messages
# response['channels'][0]['pending_messages']
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
filter := map[string]interface{}{"type": "messaging"}
resp, _ := c.QueryChannels(ctx, &QueryOption{Filter: filter})

fmt.Println("Pending Messages: ", resp.Channels[0].PendingMessages)
```

</codetabs-item>

</codetabs>

## Committing pending messages

Calling the commit message endpoint will promote a pending message into a normal message. This message will then be visible to other users and any events/push notifications associated with the message will be sent.

The commit message endpoint is server-side only.

<codetabs>

<codetabs-item value="nodejs" label="Node">

```js
await serverClient.commitMessage(id);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
Message.commit("pending_message_id").request()
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->commitMessage($msgId);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
@client.commit_message('message-1')
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.commit_message("message-1")
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
channel.CommitMessage(ctx, "message-id")
```

</codetabs-item>

</codetabs>

If a message has been in the pending state longer than the `timeout_ms` defined for your app, then the pending message will be deleted. The default timeout for a pending message is 3 days.

---

This page was last updated at 2025-08-15T14:14:58.882Z.

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