# Custom Commands Webhook

Stream supports several slash commands out of the box:

- **/giphy**  query
- **/ban**  @userid reason
- **/unban**  @userid
- **/mute**  @userid
- **/unmute**  @userid

Additionally, it’s possible to add your own commands.

By using Custom Commands, you can receive all messages sent using a specific slash command, eg. `/ticket` , in your application. When configured, every slash command message happening in a Stream Chat application will propagate to an endpoint via an HTTP POST request.

<Tabs>

```js label="JavaScript"
const message = {
  text: "/ticket suspicious transaction with id 1234",
};
const response = await channel.sendMessage(message);
```

```php label="PHP"
$client->sendEvent("messaging", "channel-id", new Models\SendEventRequest(
    event: new Models\EventRequest(
        type: "custom",
        userID: $userId,
        custom: (object)["text" => "/ticket suspicious transaction with id 1234"],
    ),
));
```

```python label="Python"
from getstream.models import EventRequest

channel.send_event(
    event=EventRequest(
        type="custom",
        user_id=user["id"],
        custom={"text": "/ticket suspicious transaction with id 1234"},
    )
)
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.send_event('messaging', 'channel-id', Models::SendEventRequest.new(
  event: Models::EventRequest.new(
    type: 'ticket_created',
    user_id: user['id'],
    custom: { 'text' => '/ticket suspicious transaction with id 1234' }
  )
))
```

```go label="Go"
channel := client.Chat().Channel("messaging", "channel-id")
channel.SendEvent(ctx, &getstream.SendEventRequest{
  Event: getstream.EventRequest{
    Type: "typing.start",
    Custom: map[string]any{"text": "/ticket suspicious transaction with id 1234"},
  },
})
```

```java label="Java"
chat.channel("channel-type", "channel-id")
    .sendEvent(SendEventRequest.builder()
        .event(EventRequest.builder()
            .type("typing.start")
            .userID(userId)
            .custom(Map.of("text", "/ticket suspicious transaction with id 1234"))
            .build())
        .build());
```

```csharp label="C#"
await chat.SendEventAsync(channel.Type, channel.Id, new SendEventRequest
{
    Event = new EventRequest
    {
        Type = "typing.start",
        UserID = user.Id,
        Custom = new Dictionary<string, object> { { "text", "/ticket suspicious transaction with id 1234" } },
    },
});
```

</Tabs>

Setting up your Custom Command consists of the following steps:

1. Registering your Custom Command

2. Configure a Channel Type

3. Configuring a Custom Action Handler URL

4. Implement a handler for your Custom Command

## Registering Custom Commands

The API provides methods to create, list, get, update, and delete Custom Command definitions. These determine which commands are allowed to be used and how they're presented to the user by providing a description of the command.

### Command Fields

| name        | type   | description                                            | default | optional |
| ----------- | ------ | ------------------------------------------------------ | ------- | -------- |
| name        | string | name of the command                                    | -       | ✓        |
| description | string | description, shown in commands auto-completion         | -       | ✓        |
| args        | string | arguments help text, shown in commands auto-completion | -       | ✓        |
| set         | string | set name used for grouping commands                    | -       | ✓        |

### Creating a Command

<Tabs>

```js label="JavaScript"
await client.createCommand({
  name: "ticket",
  description: "Create a support ticket",
  args: "[description]",
  set: "support_commands_set",
});
```

```php label="PHP"
$client->createCommand(new Models\CreateCommandRequest(
    name: "ticket",
    description: "Create a support ticket",
    args: "[description]",
    set: "support_commands_set",
));
```

```python label="Python"
client.chat.create_command(name="ticket", description="Create a support ticket", args="[description]", set="support_commands_set")
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.create_command(Models::CreateCommandRequest.new(
  name: 'ticket',
  description: 'Create a support ticket',
  args: '[description]',
  set: 'support_commands_set'
))
```

```go label="Go"
client.Chat().CreateCommand(ctx, &getstream.CreateCommandRequest{
  Name:        "ticket",
  Description: "Create a support ticket",
  Args:        getstream.PtrTo("[description]"),
  Set:         getstream.PtrTo("support_commands_set"),
})
```

```java label="Java"
chat.createCommand(CreateCommandRequest.builder()
    .name("ticket")
    .description("Create a support ticket")
    .args("[description]")
    .set("support_commands_set")
    .build()).execute();
```

```csharp label="C#"
await chat.CreateCommandAsync(new CreateCommandRequest
{
    Name = "ticket",
    Description = "Create a support ticket",
    Args = "[description]",
    Set = "support_commands_set",
});
```

</Tabs>

### List Commands

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

<Tabs>

```js label="JavaScript"
await client.listCommands();
```

```php label="PHP"
$response = $client->listCommands();
```

```python label="Python"
client.chat.list_commands()
```

```ruby label="Ruby"
require 'getstream_ruby'

client.chat.list_commands
```

```go label="Go"
client.Chat().ListCommands(ctx, &getstream.ListCommandsRequest{})
```

```java label="Java"
chat.listCommands(ListCommandsRequest.builder().build()).execute();
```

```csharp label="C#"
await chat.ListCommandsAsync();
```

</Tabs>

### Get a Command

You can retrieve a custom command definition.

<Tabs>

```js label="JavaScript"
await client.getCommand("ticket");
```

```php label="PHP"
$response = $client->getCommand("ticket");
```

```python label="Python"
client.chat.get_command("ticket")
```

```ruby label="Ruby"
require 'getstream_ruby'

client.chat.get_command('ticket')
```

```go label="Go"
client.Chat().GetCommand(ctx, "ticket", &getstream.GetCommandRequest{})
```

```java label="Java"
chat.getCommand("ticket", GetCommandRequest.builder().build()).execute();
```

```csharp label="C#"
await chat.GetCommandAsync("ticket");
```

</Tabs>

### Edit a Command

Custom command _description_, _args_ & _set_ can be changed. Only the fields that must change need to be provided, fields that are not provided to this API will remain unchanged.

<Tabs>

```js label="JavaScript"
await client.updateCommand("ticket", {
  description: "Create customer support tickets",
});
```

```php label="PHP"
$client->updateCommand("ticket", new Models\UpdateCommandRequest(
    description: "Create customer support tickets",
));
```

```python label="Python"
client.chat.update_command("ticket", description="Create customer support tickets")
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.update_command('ticket', Models::UpdateCommandRequest.new(
  description: 'Create customer support tickets'
))
```

```go label="Go"
client.Chat().UpdateCommand(ctx, "ticket", &getstream.UpdateCommandRequest{
  Description: "Create customer support tickets",
})
```

```java label="Java"
chat.updateCommand("ticket", UpdateCommandRequest.builder()
    .description("Create customer support tickets")
    .build()).execute();
```

```csharp label="C#"
await chat.UpdateCommandAsync("ticket", new UpdateCommandRequest
{
    Description = "Create customer support tickets",
});
```

</Tabs>

### Remove a Command

You can remove a custom command definition.

<Tabs>

```js label="JavaScript"
await client.deleteCommand("ticket");
```

```php label="PHP"
$response = $client->deleteCommand("ticket");
```

```python label="Python"
client.chat.delete_command("ticket")
```

```ruby label="Ruby"
require 'getstream_ruby'

client.chat.delete_command('ticket')
```

```go label="Go"
client.Chat().DeleteCommand(ctx, "ticket", &getstream.DeleteCommandRequest{})
```

```java label="Java"
chat.deleteCommand("ticket", DeleteCommandRequest.builder().build()).execute();
```

```csharp label="C#"
await chat.DeleteCommandAsync("ticket");
```

</Tabs>

<admonition type="info">

You cannot delete a custom command if there are any active channel configurations referencing it explicitly.

</admonition>

## Configure a Channel Type

In order to be able to use this command in a channel, we’ll need to create, or update an existing, channel type to include the `ticket` command.

<Tabs>

```js label="JavaScript"
await client.createChannelType({
  name: "support-channel-type",
  commands: ["ticket"],
});
```

```php label="PHP"
$client->createChannelType(new Models\CreateChannelTypeRequest(
    name: "support-channel-type",
    commands: ["ticket"],
));
```

```python label="Python"
client.chat.create_channel_type(name="support-channel-type", automod="disabled", automod_behavior="flag", max_message_length=5000, commands=["ticket"])
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.create_channel_type(Models::CreateChannelTypeRequest.new(
  name: 'support-channel-type',
  commands: ['ticket']
))
```

```go label="Go"
client.Chat().CreateChannelType(ctx, &getstream.CreateChannelTypeRequest{
  Name:              "support-channel-type",
  Automod:           "disabled",
  AutomodBehavior:   "flag",
  MaxMessageLength:  5000,
  Commands:          []string{"ticket"},
})
```

```java label="Java"
chat.createChannelType(CreateChannelTypeRequest.builder()
    .name("support-channel-type")
    .automod("disabled")
    .automodBehavior("flag")
    .maxMessageLength(5000)
    .commands(List.of("ticket"))
    .build()).execute();
```

```csharp label="C#"
await chat.CreateChannelTypeAsync(new CreateChannelTypeRequest
{
    Name = "support-channel-type",
    Commands = new List<string> { "ticket" },
});
```

</Tabs>

## Configure a Custom Action URL

In order to use the defined custom commands, you will first have to set an endpoint URL in the App Settings.

<Tabs>

```js label="JavaScript"
await client.updateAppSettings({
  custom_action_handler_url:
    "https://example.com/webhooks/stream/custom-commands?type={type}",
});
```

```php label="PHP"
$client->updateApp(new Models\UpdateAppRequest(
    customActionHandlerUrl: "https://example.com/webhooks/stream/custom-commands?type={type}",
));
```

```python label="Python"
client.update_app(custom_action_handler_url="https://example.com/webhooks/stream/custom-commands?type={type}")
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.common.update_app(Models::UpdateAppRequest.new(
  custom_action_handler_url: 'https://example.com/webhooks/stream/custom-commands?type={type}'
))
```

```java label="Java"
client.updateApp(UpdateAppRequest.builder()
    .customActionHandlerUrl("https://example.com/webhooks/stream/custom-commands?type={type}")
    .build()).execute();
```

```csharp label="C#"
await client.UpdateAppAsync(new UpdateAppRequest
{
    CustomActionHandlerUrl = "https://example.com/webhooks/stream/custom-commands?type={type}",
});
```

```go label="Go"
client.UpdateApp(ctx, &getstream.UpdateAppRequest{
  CustomActionHandlerUrl: getstream.PtrTo("https://example.com/webhooks/stream/custom-commands?type={type}"),
})
```

</Tabs>

<admonition type="info">

You can use a `{type}` variable substitution in the URL to pass on the name of the command that was triggered. See [Webhooks Overview](/chat/docs/go-golang/webhooks_overview/) for more information on URL configuration

</admonition>

## Request format

Your endpoint will receive a POST request with a JSON encoded body containing: message, user and form_data objects. The form_data object will contain values of the interactions initiated by Attachment.

<Tabs>

```json label="JSON"
{
  "message": {
    "id": "xyz",
    "text": "/ticket suspicious transaction with id 1234",
    "command": "ticket",
    "args": "suspicious transaction with id 1234",
    "html": "",
    "type": "regular",
    "cid": "messaging:xyz",
    "created_at": "2021-11-16T12:56:59.854Z",
    "updated_at": "2021-11-16T12:56:59.854Z",
    "attachments": [],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": null,
    "reaction_scores": null,
    "reply_count": 0,
    "mentioned_users": [],
    "silent": false
  },
  "user": {
    "id": "17f8ab2c-c7e7-4564-922b-e5450dbe4fe7",
    "Custom": {
      "name": "jdoe"
    },
    "role": "user",
    "banned": false,
    "online": false
  },
  "form_data": {
    "action": "submit",
    "name": "John Doe",
    "email": "john@doe.com"
  }
}
```

</Tabs>

## Response format

If you intend to make any change to the message, you should return a JSON encoded response with the same message structure. Please note that not all message fields can be changed, the full list of fields that can be modified is available in the **rewriting messages** section.

## Discarding messages

Your endpoint can decide to reject the command and return a user message. To do that the endpoint must return a regular message with type set to error.

<Tabs>

```json label="JSON"
{
  "message": {
    "type": "error",
    "text": "invalid arguments for command /ticket"
  }
}
```

</Tabs>

## Rewriting messages

You can also decide to modify the message, in that case you return the updated version of the message and it will overwrite the user input.

<Tabs>

```json label="JSON"
{
  "message": {
    "text": "Ticket #85736 has been created"
  }
}
```

</Tabs>

Interactions can be initiated either using Attachment actions:

<Tabs>

```json label="JSON"
{
  "message": {
  "text": "Ticket #85736 has been created",
  "attachments": [
    {
      "type": "text",
      "actions": [
        {
          "name": "action",
          "text": "Send",
          "style": "primary",
          "type": "button",
          "value": "submit"
        },
        {
          "name": "action",
          "text": "Cancel",
          "style": "default",
          "type": "button",
          "value": "cancel"
        }
    }
  ]
  }
}
```

</Tabs>

<admonition type="info">

You can find more information on message rewrite in [Before Message Send Webhook](/chat/docs/go-golang/before_message_send_webhook/) page

</admonition>

## Performance considerations

Your webhook endpoint will be part of the send message transaction,
so you should avoid performing any remote calls or potentially slow
operations while processing the request. Stream Chat will give your
endpoint 1 second of time to reply. If your endpoint is not available
(ie. returns a response with status codes 4xx or 5xx) or takes too long,
Stream Chat will continue with the execution and save the message as
usual.

To make sure that an outage on the hook does not impact your
application, Stream will pause your webhook once it is considered
unreachable and it will automatically resume once the webhook is found
to be healthy again.

## Example code

An example of how to handle incoming Custom Command requests can be found in [this repo](https://github.com/GetStream/customcommand).


---

This page was last updated at 2026-04-22T16:43:07.871Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/go-golang/custom_commands_webhook/](https://getstream.io/chat/docs/go-golang/custom_commands_webhook/).