---
title: "Webhook de SMS"
description: "Avisos do percurso de cada mensagem — e, quando alguém responde, o texto da resposta."
lang: pt-BR
canonical: https://doc.apibrasil.io/webhooks/api-sms
markdown: https://doc.apibrasil.io/webhooks/api-sms.md
source: apibrasil-documentation
---

# Webhook de SMS

> Avisos do percurso de cada mensagem — e, quando alguém responde, o texto da resposta.

Cada mensagem avisa a cada passo que dá. E quando o destinatário responde, a resposta chega no seu endpoint com o texto dela — SMS de mão dupla, sem você perguntar nada.

## Como receber

A URL vai no corpo da própria requisição de envio, em webhook_url, e é o provedor que a chama. O esqueleto abaixo desempacota o array, responde 200 e separa a resposta dos demais avisos.

**Onde registrar a URL e o que cai nela**

| Campo | O que recebe | Eventos |
| --- | --- | --- |
| `webhook_url` | Todos os avisos do canal, na mesma URL. | todos os eventos do canal |

Os eventos deste canal chegam em sequência para o mesmo id, na ordem abaixo.

## Eventos (4)

### `inserted_for_processing` — Aceita para processamento

**Quando dispara:** Assim que a mensagem entra na fila. Ainda não foi validada nem enviada.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `version` | string | A versão do formato. As capturas atuais trazem "v2". |
| `id` | string | Identificador da mensagem. É o MESMO nos avisos de um envio — use-o para juntá-los. |
| `status` | string | O estado da mensagem. É por ele que se roteia: não há campo de tipo de evento. |
| `number` | string | O destinatário, só dígitos e com DDD. Sem o + e sem o código do país. |
| `created_at` | string | Quando a mensagem entrou na fila. |
| `sent_at` | string \| null | Quando saiu. Nulo enquanto não saiu. |
| `delivered_at` | string \| null | Quando o aparelho confirmou. Nulo nos quatro avisos capturados. |
| `error_at` | string \| null | Quando falhou. Nulo nos quatro avisos capturados. |
| `blocked_at` | string \| null | Quando foi bloqueada. Nulo nos quatro avisos capturados. |

**Corpo de exemplo, como chega**

```json
[
  {
    "version": "v2",
    "id": "9098235346a90edb2b868d892310556",
    "status": "inserted_for_processing",
    "number": "31994359434",
    "created_at": "2026-08-28 02:08:50",
    "sent_at": "2026-08-28 02:08:50",
    "delivered_at": null,
    "error_at": null,
    "blocked_at": null
  }
]
```

### `valid` — Número validado

**Quando dispara:** Quando o destinatário passa na validação de formato e de bloqueio.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `version` | string | A versão do formato. As capturas atuais trazem "v2". |
| `id` | string | Identificador da mensagem. É o MESMO nos avisos de um envio — use-o para juntá-los. |
| `status` | string | O estado da mensagem. É por ele que se roteia: não há campo de tipo de evento. |
| `number` | string | O destinatário, só dígitos e com DDD. Sem o + e sem o código do país. |
| `created_at` | string | Quando a mensagem entrou na fila. |
| `sent_at` | string \| null | Quando saiu. Nulo enquanto não saiu. |
| `delivered_at` | string \| null | Quando o aparelho confirmou. Nulo nos quatro avisos capturados. |
| `error_at` | string \| null | Quando falhou. Nulo nos quatro avisos capturados. |
| `blocked_at` | string \| null | Quando foi bloqueada. Nulo nos quatro avisos capturados. |

**Corpo de exemplo, como chega**

```json
[
  {
    "version": "v2",
    "id": "9098235346a90edb2b868d892310556",
    "status": "valid",
    "number": "31994359434",
    "created_at": "2026-08-27 23:08:52",
    "sent_at": "2026-08-27 23:08:52",
    "delivered_at": null,
    "error_at": null,
    "blocked_at": null
  }
]
```

### `sent_to_carrier` — Entregue à operadora

**Quando dispara:** Quando a operadora recebe a mensagem. Não é a confirmação do aparelho — delivered_at segue nulo.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `version` | string | A versão do formato. As capturas atuais trazem "v2". |
| `id` | string | Identificador da mensagem. É o MESMO nos avisos de um envio — use-o para juntá-los. |
| `status` | string | O estado da mensagem. É por ele que se roteia: não há campo de tipo de evento. |
| `number` | string | O destinatário, só dígitos e com DDD. Sem o + e sem o código do país. |
| `created_at` | string | Quando a mensagem entrou na fila. |
| `sent_at` | string \| null | Quando saiu. Nulo enquanto não saiu. |
| `delivered_at` | string \| null | Quando o aparelho confirmou. Nulo nos quatro avisos capturados. |
| `error_at` | string \| null | Quando falhou. Nulo nos quatro avisos capturados. |
| `blocked_at` | string \| null | Quando foi bloqueada. Nulo nos quatro avisos capturados. |

**Corpo de exemplo, como chega**

```json
[
  {
    "version": "v2",
    "id": "9098235346a90edb2b868d892310556",
    "status": "sent_to_carrier",
    "number": "31994359434",
    "created_at": "2026-08-27 23:08:53",
    "sent_at": "2026-08-27 23:08:53",
    "delivered_at": null,
    "error_at": null,
    "blocked_at": null
  }
]
```

### `reply` — O destinatário respondeu

**Quando dispara:** Quando o destinatário responde ao seu SMS. Chega com o texto dela, e com o mesmo id da mensagem original — é o que amarra a conversa.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `version` | string | A versão do formato. As capturas atuais trazem "v2". |
| `id` | string | Identificador da mensagem. É o MESMO nos avisos de um envio — use-o para juntá-los. |
| `status` | string | O estado da mensagem. É por ele que se roteia: não há campo de tipo de evento. |
| `number` | string | O destinatário, só dígitos e com DDD. Sem o + e sem o código do país. |
| `created_at` | string | Quando a mensagem entrou na fila. |
| `sent_at` | string \| null | Quando saiu. Nulo enquanto não saiu. |
| `delivered_at` | string \| null | Quando o aparelho confirmou. Nulo nos quatro avisos capturados. |
| `error_at` | string \| null | Quando falhou. Nulo nos quatro avisos capturados. |
| `blocked_at` | string \| null | Quando foi bloqueada. Nulo nos quatro avisos capturados. |
| `mensagem` | string | O texto que a pessoa respondeu. Em português no meio de um payload em inglês, e só existe neste aviso. |

**Corpo de exemplo, como chega**

```json
[
  {
    "version": "v2",
    "id": "9098235346a90edb2b868d892310556",
    "status": "reply",
    "number": "31994359434",
    "created_at": "2026-08-27 23:13:13",
    "sent_at": "2026-08-27 23:13:13",
    "delivered_at": null,
    "error_at": null,
    "blocked_at": null,
    "mensagem": "The best apibrasil.io"
  }
]
```

## O que costuma pegar

- **O corpo é um array, não um objeto** — Todo POST chega como [ { … } ], com um aviso dentro. Ler body.status devolve undefined — o caminho é body[0].status.
- **As datas trocam de fuso entre os avisos** — Na captura de referência, o primeiro aviso veio em UTC e os dois seguintes em horário de Brasília, para o mesmo id. Ordenar por created_at coloca o primeiro evento por último, três horas à frente. Use a ordem de chegada, não a data.
- **O campo do texto da resposta chama mensagem, em português** — Todos os outros campos do payload estão em inglês — number, created_at, sent_at. Só o texto da resposta vem como mensagem. Ele aparece exclusivamente no aviso de status reply; nos outros três o campo não existe.
- **Você registra a URL no envio, não no painel** — O webhook_url vai no corpo da própria requisição de envio do SMS. O gateway o repassa ao provedor, e é o provedor quem chama a sua URL.

## Boas práticas

- **Responda 200 antes de processar** — Confirme o recebimento e faça o trabalho pesado depois, fora do ciclo da requisição. Um endpoint que grava no banco antes de responder transforma lentidão do banco em timeout de webhook.
- **A chave de idempotência é id + status** — O mesmo id chega uma vez por status — quatro POSTs para uma mensagem que foi respondida. Deduplicar só por id descartaria três avisos legítimos.
- **HTTPS, pelo que o corpo carrega** — O payload leva o número do destinatário e, no aviso de reply, o texto que a pessoa escreveu. Em HTTP isso trafega legível para qualquer intermediário da rede.

## Receptores de exemplo

**Node**

```node
app.post("/webhook/sms", express.json(), (req, res) => {
  // O corpo é um ARRAY com um aviso dentro.
  const [aviso] = req.body
  if (!aviso) return res.sendStatus(400)

  // Responda antes de processar: o trabalho pesado sai do ciclo da requisição.
  res.sendStatus(200)

  if (aviso.status === "reply") {
    console.log(`${aviso.number} respondeu: ${aviso.mensagem}`)
    return
  }

  // O mesmo id chega uma vez por status — a chave é o par, não só o id.
  console.log(`${aviso.id} agora está ${aviso.status}`)
})
```

**Python**

```python
@app.post("/webhook/sms")
def webhook_sms():
    # O corpo é uma LISTA com um aviso dentro.
    avisos = request.get_json(silent=True) or []
    if not avisos:
        return "", 400

    aviso = avisos[0]

    if aviso["status"] == "reply":
        print(f"{aviso['number']} respondeu: {aviso['mensagem']}")
    else:
        # O mesmo id chega uma vez por status — a chave é o par, não só o id.
        print(f"{aviso['id']} agora está {aviso['status']}")

    return "", 200
```

**PHP**

```php
<?php

// O corpo é um ARRAY com um aviso dentro.
$avisos = json_decode(file_get_contents('php://input'), true) ?: [];
$aviso = $avisos[0] ?? null;

if (!$aviso) {
    http_response_code(400);
    exit;
}

http_response_code(200);

if ($aviso['status'] === 'reply') {
    error_log("{$aviso['number']} respondeu: {$aviso['mensagem']}");
} else {
    // O mesmo id chega uma vez por status — a chave é o par, não só o id.
    error_log("{$aviso['id']} agora está {$aviso['status']}");
}
```

## Segurança

- **Este webhook NÃO É ASSINADO.** Não há HMAC, cabeçalho de assinatura nem
  segredo compartilhado. Não invente uma verificação de assinatura: ela não
  existe e o código ficaria rejeitando tudo.
- Trate o corpo como entrada NÃO CONFIÁVEL. Qualquer um que descubra a URL
  pode postar nela.
- Use um caminho impossível de adivinhar (um UUID no path, por exemplo) e
  sirva só por HTTPS — o corpo carrega o telefone do destinatário e, no aviso
  de resposta, o texto que a pessoa escreveu.
- Antes de AGIR sobre um aviso, confira se o `id` corresponde a uma mensagem
  que você mesmo enviou. Sem isso, um terceiro forja um `reply` e dispara a
  sua automação.
- Não registre o conteúdo de `mensagem` em log sem necessidade: é conteúdo
  escrito por um usuário final.

## Ver também

- Webhooks: [Webhooks](https://doc.apibrasil.io/webhooks.md)
- Índice da documentação: https://doc.apibrasil.io/llms.txt
- Versão HTML desta página: https://doc.apibrasil.io/webhooks/api-sms
