Webhooks são callbacks HTTP configurados para cada conta. Eles são acionados quando ações como a criação de uma mensagem ocorrem no Chatwoot. Vários webhooks podem ser criados para uma única conta.

## Como adicionar um webhook?

**Passo 1.** Acesse **Configurações → Integrações → Webhooks**. Clique no botão "Configurar".

![](https://app.chatwoot.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBNzU1VHc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--d54d34aa968f1291a3d3882a29abbd8056eb6a09/how-to-find-webhooks-setting-in-chatwoot.png)

**Passo 2.** Clique no botão "Adicionar novo webhook". Um modal será aberto. Aqui, insira a URL para a qual a requisição POST deve ser enviada. Em seguida, você precisa selecionar os eventos aos quais deseja se inscrever. Esta opção permite que você escute apenas pelos eventos relevantes no Chatwoot.

![](https://app.chatwoot.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBOEo1VHc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--99a0d32f0c3f8e39f81b49d9fa834ac97efa4f0f/webhook%20setting.png)

O Chatwoot enviará uma requisição POST com o seguinte payload para as URLs configuradas para diversas atualizações em sua conta.

### Exemplo de payload de webhook

```
{

  "event": "message_created", // Nome do evento
  "id": "1", // ID da mensagem
  "content": "Hi", // Conteúdo da mensagem
  "created_at": "2020-03-03 13:05:57 UTC", // Hora em que a mensagem foi enviada
  "message_type": "incoming", // Terá o valor incoming, outgoing ou template. O usuário do widget envia mensagens incoming, o agente envia mensagens outgoing para o usuário.
  "content_type": "enum", // Isto é um enum, pode ser input_select, cards, form ou text. O message_type será template se o content_type for um destes. O valor padrão é text
  "content_attributes": {} // Será um objeto, valores diferentes são definidos abaixo
  "source_id": "", // Será o id externo se a caixa de entrada for uma integração com Twitter ou Facebook.
  "sender": { // Proverá detalhes do agente que enviou a mensagem
    "id": "1",
    "name": "Agent",
    "email": "agent@example.com"
  },
  "contact": { // Proverá detalhes do usuário que enviou a mensagem
    "id": "1",
    "name": "contact-name"
  },
  "conversation": { // Proverá detalhes da conversa
    "display_id": "1", // ID da conversa visível no dashboard.
    "additional_attributes": {
      "browser": {
        "device_name": "Macbook",
        "browser_name": "Chrome",
        "platform_name": "Macintosh",
        "browser_version": "80.0.3987.122",
        "platform_version": "10.15.2"
      },
      "referer": "<http://www.chatwoot.com>",
      "initiated_at": "Tue Mar 03 2020 18:37:38 GMT-0700 (Mountain Standard Time)"
    }
  },
  "account": { // Proverá detalhes da conta
    "id": "1",
    "name": "Chatwoot",
  }
}
```

## Eventos de webhook suportados no Chatwoot

O Chatwoot publica vários eventos para os endpoints de webhook configurados. Se você quiser configurar um webhook, consulte o guia [aqui](https://www.chatwoot.com/docs/product/features/webhooks).

Cada evento possui sua estrutura de payload com base no tipo de modelo sobre o qual atuam. A seção a seguir descreve os principais objetos que usamos no Chatwoot e seus atributos.

## Objetos

Um payload de evento pode incluir qualquer um dos seguintes objetos. Os vários tipos de objetos suportados pelo Chatwoot estão listados abaixo.

**Conta**

```
{
  "id": "integer",
  "name": "string"
}
```

**Caixa de entrada**

```
{
"id": "integer",
"name": "string"
}
```

**Contato**

```
{
  "id": "integer",
  "name": "string",
  "avatar": "string",
  "type": "contact",
  "account": {
    // <...Objeto Conta>
  }
}
```

**Usuário**

```
{
  "id": "integer",
  "name": "string",
  "email": "string",
  "type": "user"
}
```

**Conversa**

```
{
  "additional_attributes": {
    "browser": {
      "device_name": "string",
      "browser_name": "string",
      "platform_name": "string",
      "browser_version": "string",
      "platform_version": "string"
    },
    "referer": "string",
    "initiated_at": {
      "timestamp": "iso-datetime"
    }
  },
  "can_reply": "boolean",
  "channel": "string",
  "id": "integer",
  "inbox_id": "integer",
  "contact_inbox": {
    "id": "integer",
    "contact_id": "integer",
    "inbox_id": "integer",
    "source_id": "string",
    "created_at": "datetime",
    "updated_at": "datetime",
    "hmac_verified": "boolean"
  },
  "messages": ["Array de objetos de mensagem"],
  "meta": {
    "sender": {
      // Objeto Contato
    },
    "assignee": {
      // Objeto Usuário
    }
  },
  "status": "string",
  "unread_count": "integer",
  "agent_last_seen_at": "unix-timestamp",
  "contact_last_seen_at": "unix-timestamp",
  "timestamp": "unix-timestamp",
  "account_id": "integer"
}
```

**Mensagem**

```
{
  "id": "integer",
  "content": "string",
  "message_type": "integer",
  "created_at": "unix-timestamp",
  "private": "boolean",
  "source_id": "string / null",
  "content_type": "string",
  "content_attributes": "object",
  "sender": {
    "type": "string - contact/user"
    // Objeto Usuário ou Contato
  },
  "account": {
    // Objeto Conta
  },
  "conversation": {
    // Objeto Conversa
  },
  "inbox": {
    // Objeto Caixa de entrada
  }
}
```

**Exemplo de payload de webhook**

```
{
  "event": "event_name"
  // Atributos relacionados ao evento
}
```

## Eventos de Webhook

O Chatwoot suporta os seguintes eventos de webhook. Você pode se inscrever neles ao configurar um webhook no painel ou utilizando a API.

### conversation_created

Este evento será acionado quando uma nova conversa for criada na conta. O payload do evento é o seguinte.

```
{
  "event": "conversation_created"
  // <...Atributos da Conversa>
}
```

### conversation_updated

Este evento será acionado quando houver uma alteração em algum dos atributos da conversa.

```
{
  "event": "conversation_updated",
  "changed_attributes": [
    {
      "<nome_do_atributo>": {
        "current_value": "",
        "previous_value": ""
      }
    }
  ]
  // <...Atributos da Conversa>
}
```

### conversation_status_changed

Este evento será acionado quando o status da conversa for alterado.

Nota: Se você estiver utilizando APIs de agent bot em vez de webhooks, este evento ainda não é suportado.

```
{
  "event": "conversation_status_changed"
  // <...Atributos da Conversa>
}
```

### message_created

Este evento será acionado quando uma mensagem for criada em uma conversa. O payload do evento é o seguinte.

```
{
  "event": "message_created"
  // <...Atributos da Mensagem>
}
```

### message_updated

Este evento será acionado quando uma mensagem for atualizada em uma conversa. O payload do evento é o seguinte.

```
{
  "event": "message_updated"
  // <...Atributos da Mensagem>
}
```

### webwidget_triggered

Este evento será acionado quando o usuário final abrir o widget de chat ao vivo.

```
{
  "event": "webwidget_triggered",
  "id": "",
  "contact": {
    // <...Objeto Contato>
  },
  "inbox": {
    // <...Objeto Caixa de Entrada>
  },
  "account": {
    // <...Objeto Conta>
  },
  "current_conversation": {
    // <...Objeto Conversa>
  },
  "source_id": "string",
  "event_info": {
    "initiated_at": {
      "timestamp": "date-string"
    },
    "referer": "string",
    "widget_language": "string",
    "browser_language": "string",
    "browser": {
      "browser_name": "string",
      "browser_version": "string",
      "device_name": "string",
      "platform_name": "string",
      "platform_version": "string"
    }
  }
}
```

### conversation_typing_on

Este evento é acionado quando um agente começa a digitar em uma conversa. Pode ser uma nota privada ou uma mensagem para o cliente. Você pode usar o flag `is_private` para diferenciar entre os dois.

```
{
  "event": "conversation_typing_on",
  "conversation": { ...<Objeto Conversa> },
  "user": { ... <Usuário / AgentBot / Objeto Capitão> },
  "is_private": true
}
```

### conversation_typing_off

Este evento é acionado quando o agente para de digitar ou sai da janela da conversa.

```
{
  "event": "conversation_typing_off",
  "conversation": { ...<Objeto Conversa> },
  "user": { ... <Usuário / AgentBot / Objeto Capitão> },
  "is_private": true
}
```

# Verificando webhooks

O Chatwoot assina todas as requisições de webhook de saída para que seu servidor possa verificar se o payload foi enviado pelo Chatwoot e não foi adulterado. O segredo é exibido uma vez que o webhook é criado, e você pode visualizá-lo novamente no formulário de edição do webhook.

Toda requisição de webhook envia os seguintes cabeçalhos, que podem ser usados para calcular a assinatura HMAC do payload

* `X-Chatwoot-Signature`: assinatura HMAC-SHA256 prefixada com `sha256=`

* `X-Chatwoot-Timestamp`: timestamp Unix (segundos) de quando a requisição foi assinada

* `X-Chatwoot-Delivery`: ID único de entrega para o evento de webhook (quando disponível)

A assinatura é calculada assim:

```
sha256=HMAC-SHA256(webhook_secret, "{timestamp}.{raw_body}")
```

Onde:

* `webhook_secret` é o segredo associado ao webhook

* `timestamp` é o valor do cabeçalho `X-Chatwoot-Timestamp`

* `raw_body` é o corpo da requisição JSON bruta (não parseado/re-serializado)

## Passos de Verificação

1. Extraia `X-Chatwoot-Signature` e `X-Chatwoot-Timestamp` dos cabeçalhos da requisição

2. Leia o corpo bruno da requisição como bytes (não parse e re-serialize)

3. Calcule a assinatura esperada: `sha256=HMAC-SHA256(secret, "{timestamp}.{raw_body}")`

4. Compare a assinatura calculada com a recebida usando uma comparação em tempo constante

5. Opcionalmente, rejeite requisições cujo timestamp seja muito antigo para prevenir ataques de repetição (replay)

## Exemplos

### Ruby

```
def verify_signature(raw_body, timestamp, received_signature, secret)
  expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}.#{raw_body}")}"
  ActiveSupport::SecurityUtils.secure_compare(expected, received_signature)
end
```

### Python

```
import hmac
import hashlib

def verify_signature(raw_body: bytes, timestamp: str, received_signature: str, secret: str) -> bool:
    message = f"{timestamp}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_signature)
```

### Node.js

```

const crypto = require("crypto");

function verifySignature(rawBody, timestamp, receivedSignature, secret) {
  const message = `${timestamp}.${rawBody}`;
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(message).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(receivedSignature)
  );
}
```

### Go

```
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

func verifySignature(rawBody []byte, timestamp, receivedSignature, secret string) bool {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(fmt.Sprintf("%s.%s", timestamp, rawBody)))
	expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(receivedSignature))
}
```

## Notas Importantes

* Sempre utilize o **corpo bruto da requisição** para verificação. Fazer o parse do JSON e depois re-serializar pode alterar a ordenação das chaves, espaços ou escape unicode, o que resultará em uma assinatura diferente.

* Sempre utilize **comparação em tempo constante** (ex: `hmac.compare_digest`, `crypto.timingSafeEqual`, `ActiveSupport::SecurityUtils.secure_compare`) para prevenir ataques de timing.

* Considere rejeitar requisições com timestamp mais antigos que 5 minutos para mitigar ataques de repetição.