> ## Documentation Index
> Fetch the complete documentation index at: https://docs.socialsell.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receba notificações em tempo real quando eventos ocorrem na SocialSell.

## O que são webhooks?

Webhooks permitem que a SocialSell envie notificações automáticas para o seu sistema quando eventos ocorrem — como um novo contato criado, um negócio ganho ou uma mensagem recebida. Em vez de fazer polling constante na API, você recebe os dados imediatamente.

## Eventos disponíveis

| Evento               | Descrição                                    |
| -------------------- | -------------------------------------------- |
| `contact.created`    | Um novo contato foi criado                   |
| `contact.updated`    | Um contato foi atualizado                    |
| `deal.created`       | Um novo negócio foi criado                   |
| `deal.stage.changed` | Um negócio mudou de etapa no funil de venda  |
| `deal.won`           | Um negócio foi marcado como ganho            |
| `deal.lost`          | Um negócio foi marcado como perdido          |
| `message.received`   | Uma nova mensagem foi recebida de um contato |

<Note>
  Mais eventos serão adicionados em versões futuras. Acompanhe o roadmap em [socialsell.canny.io](https://socialsell.canny.io).
</Note>

## Criando uma assinatura

Requer o escopo `write:webhooks` e plano **Growth ou superior**.

```bash theme={null}
curl -X POST https://api.socialsell.ai/v1/webhooks \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Notificações ERP",
    "url": "https://meusistema.com/webhook/socialsell",
    "events": ["contact.created", "deal.won", "deal.stage.changed"]
  }'
```

A resposta inclui o campo `secret` — armazene-o com segurança para validar as entregas:

```json theme={null}
{
  "data": {
    "id": "664f1a2b3c4d5e6f78901234",
    "name": "Notificações ERP",
    "url": "https://meusistema.com/webhook/socialsell",
    "events": ["contact.created", "deal.won", "deal.stage.changed"],
    "status": "active",
    "secret": "whsec_a1b2c3d4e5f6...",
    "created_at": "2026-06-10T15:00:00.000Z"
  }
}
```

<Warning>
  O campo `secret` é retornado **apenas na criação**. Não é possível recuperá-lo depois. Armazene-o como variável de ambiente.
</Warning>

## Formato do payload

Cada evento entregue tem o seguinte formato:

```json theme={null}
{
  "id": "evt_01jx8kz3m4n5p6q7r8s9t0u1v",
  "event": "deal.won",
  "organization_id": "664a1b2c3d4e5f6789012345",
  "created_at": "2026-06-10T15:30:00.000Z",
  "data": {
    "id": "664f1a2b3c4d5e6f78901234",
    "title": "Proposta Empresa XYZ",
    "value": 5000,
    "status": "won",
    "pipeline": { "id": "...", "name": "Funil Principal" },
    "stage": { "name": "Fechamento" },
    "assigned_to": { "id": "...", "name": "João Silva" }
  }
}
```

## Validando a assinatura

Cada requisição inclui o header `X-SocialSell-Signature` com um HMAC-SHA256 do corpo usando seu `secret`:

```
X-SocialSell-Signature: sha256=a1b2c3d4e5f6...
```

**Sempre valide a assinatura** antes de processar o payload:

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function validateWebhookSignature(body, signature, secret) {
    const expected = 'sha256=' + crypto
      .createHmac('sha256', secret)
      .update(typeof body === 'string' ? body : JSON.stringify(body))
      .digest('hex');
    
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  }

  // No seu handler Express:
  app.post('/webhook/socialsell', express.raw({ type: 'application/json' }), (req, res) => {
    const signature = req.headers['x-socialsell-signature'];
    const secret = process.env.SOCIALSELL_WEBHOOK_SECRET;
    
    if (!validateWebhookSignature(req.body, signature, secret)) {
      return res.status(401).send('Assinatura inválida');
    }
    
    const event = JSON.parse(req.body);
    
    switch (event.event) {
      case 'deal.won':
        // processar negócio ganho
        break;
      case 'contact.created':
        // processar novo contato
        break;
    }
    
    res.status(200).send('OK');
  });
  ```

  ```python Python (Flask) theme={null}
  import hmac
  import hashlib
  import os
  from flask import Flask, request, abort

  app = Flask(__name__)

  def validate_signature(body, signature, secret):
      expected = 'sha256=' + hmac.new(
          secret.encode(),
          body,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)

  @app.route('/webhook/socialsell', methods=['POST'])
  def webhook():
      signature = request.headers.get('X-SocialSell-Signature', '')
      secret = os.environ['SOCIALSELL_WEBHOOK_SECRET']
      
      if not validate_signature(request.data, signature, secret):
          abort(401)
      
      event = request.json
      
      if event['event'] == 'deal.won':
          # processar negócio ganho
          pass
      
      return 'OK', 200
  ```
</CodeGroup>

## Política de retentativas

A SocialSell tenta entregar cada evento com as seguintes regras:

* **Timeout**: 10 segundos para o endpoint responder
* **Sucesso**: resposta HTTP `2xx`
* **Falha**: qualquer outro status ou timeout
* **Retentativas**: após falha, tenta novamente com backoff exponencial
* **Desativação automática**: após múltiplas falhas consecutivas, a assinatura é pausada automaticamente (campo `consecutive_failures`)

<Tip>
  Responda com `200 OK` o mais rápido possível e processe o evento de forma assíncrona (ex: em uma fila). Isso evita timeouts causados por processamento lento.
</Tip>

## Gerenciamento de assinaturas

* [Listar webhooks](/api-reference/webhooks/list)
* [Criar webhook](/api-reference/webhooks/create)
* [Atualizar webhook](/api-reference/webhooks/update)
* [Deletar webhook](/api-reference/webhooks/delete)

## Idempotência

Eventos podem ser entregues mais de uma vez em casos de falha. Use o campo `id` do evento para garantir idempotência no processamento:

```javascript theme={null}
// Armazene IDs já processados para evitar duplicatas
const processedEvents = new Set();

function handleWebhook(event) {
  if (processedEvents.has(event.id)) return; // já processado
  processedEvents.add(event.id);
  
  // processar...
}
```
