Webhook de SMS na prática: status de entrega em tempo real
本文目前仅提供葡萄牙语版本。
Um webhook de SMS é um endpoint HTTP da sua aplicação que o provedor chama automaticamente a cada evento relevante da mensagem: em vez de você consultar o status em loop (polling), a plataforma envia um POST com o relatório de entrega (DLR) ou com a resposta do destinatário assim que o evento acontece. Na SMSGo, você cadastra a URL uma única vez via PUT /v1/account/webhook e passa a receber dois eventos — sms.status e sms.reply — assinados com HMAC-SHA256 no header X-SMSGo-Signature.
Por que polling não escala
Depois de enviar um SMS via API, a resposta imediata diz apenas que a mensagem entrou na fila. O que importa — entregue, falhou, respondida — chega depois, no ritmo da operadora. Consultar o endpoint de detalhe a cada poucos segundos multiplica requisições inúteis, atrasa a reação do seu sistema e ainda esbarra em rate limits.
O volume só agrava o problema: segundo a Juniper Research, o tráfego global de mensagens de negócios (A2P) deve crescer de 2 trilhões de mensagens em 2025 para quase 3 trilhões até 2030. Com webhook, a relação se inverte: um evento, um POST, zero desperdício.
Os dois eventos: sms.status e sms.reply
A SMSGo envia um POST em JSON para a sua URL em duas situações:
sms.status— relatório de entrega (DLR). O campodatatrazsendId,phoneestatus, que pode serdelivered,failedouin_progress.sms.reply— resposta do destinatário (mensagem MO). OdatatrazfromPhone,messageereceivedAt.
O corpo completo tem sempre o mesmo envelope:
{
"id": "3f1c0a2e-uuid-do-evento",
"event": "sms.status",
"data": { "sendId": "uuid-do-envio", "phone": "5511999990000", "status": "delivered" },
"sentAt": "2026-07-02T12:00:00.000Z"
}
O id identifica o evento (ele também vai no header X-SMSGo-Event-Id) e o sentAt carimba o momento do disparo em ISO-8601 UTC. O sms.reply merece atenção especial: segundo a Gartner, o SMS alcança taxas de resposta de até 45%, então tratar as respostas — inclusive pedidos de opt-out, uma exigência prática da LGPD — não é opcional.
Configurando o webhook na SMSGo
Cadastre a URL no painel (Minha conta, seção API) ou direto pela API:
curl -X PUT https://api.smsgo.com.br/v1/account/webhook \
-H "Authorization: Bearer SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "url": "https://app.suaempresa.com.br/webhooks/smsgo" }'
Três regras valem em produção: a URL precisa ser HTTPS, apontar para um host público (endereços internos são recusados) e responder sem redirecionamentos — redirects não são seguidos. Ao configurar, você recebe um secret: guarde-o em variável de ambiente, pois é com ele que a assinatura de cada entrega é gerada.
Verificando a assinatura HMAC-SHA256 (Node.js/Express)
Uma URL pública aceita POST de qualquer um — sem verificação, um atacante pode forjar um evento de entrega ou uma resposta falsa. Por isso toda requisição da SMSGo traz o header X-SMSGo-Signature: sha256=..., o HMAC-SHA256 do corpo bruto calculado com o seu secret. O receptor DEVE validar antes de confiar:
const express = require('express')
const crypto = require('crypto')
const app = express()
app.post(
'/webhooks/smsgo',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.get('X-SMSGo-Signature') || ''
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.SMSGO_WEBHOOK_SECRET)
.update(req.body) // corpo BRUTO, antes de qualquer parse
.digest('hex')
const a = Buffer.from(signature)
const b = Buffer.from(expected)
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('assinatura invalida')
}
const event = JSON.parse(req.body.toString('utf8'))
res.sendStatus(200) // confirme rapido; processe depois
if (event.event === 'sms.status') {
// event.data: { sendId, phone, status }
}
if (event.event === 'sms.reply') {
// event.data: { fromPhone, message, receivedAt }
}
}
)
app.listen(3000)
Dois detalhes fazem a diferença. Primeiro, o HMAC é calculado sobre o corpo bruto: se um middleware de JSON reserializar o payload antes da verificação, a assinatura nunca vai bater — daí o express.raw na rota. Segundo, a comparação usa timingSafeEqual, imune a ataques de temporização, em vez de um simples comparador de strings.
Idempotência e proteção contra replay
A assinatura prova autenticidade, não frescor: uma entrega legítima capturada poderia ser reenviada depois. Como id e sentAt estão dentro do corpo assinado, feche as duas brechas após validar o HMAC:
- Rejeite eventos antigos: descarte se
sentAtestiver fora de uma janela de tolerância (5 minutos é um bom padrão) em relação ao seu relógio. - Deduplique por
id: a SMSGo reenvia o evento com backoff quando seu endpoint não responde 2xx, então o mesmo evento pode chegar mais de uma vez. Guarde osidprocessados e ignore repetições; parasms.status, o parsendId+statustambém funciona como chave natural. - Responda 2xx imediatamente e jogue o processamento pesado para uma fila. Endpoint lento vira timeout, e timeout vira retry — ou seja, mais duplicatas.
O que fazer com cada status
delivered— confirme o fluxo de negócio: marque a notificação como entregue, alimente métricas de campanha, encerre o ciclo do OTP.failed— acione o plano B: tentar novamente, cair para outro canal (e-mail, push) ou marcar o número como inválido para higienizar sua base.in_progress— estado intermediário; útil para exibir "enviando" na sua interface, sem ação de negócio.sms.reply— roteie a resposta: palavras como SAIR devem alimentar sua lista de opt-out na hora; o resto pode abrir um atendimento ou seguir um fluxo de duas vias.
Erros comuns ao receber webhooks
- Verificar a assinatura sobre o JSON já parseado em vez do corpo bruto — a causa mais comum de "assinatura não bate".
- Comparar o HMAC com == em vez de comparação de tempo constante.
- Processar de forma síncrona dentro do handler e estourar o timeout do provedor.
- Ignorar duplicatas e disparar duas vezes o mesmo efeito colateral (dois e-mails, dois estornos).
- Testar com URL http ou host interno — em produção a SMSGo exige HTTPS em host público.
Próximos passos
Se você ainda não fez o primeiro disparo, comece pelo guia de como enviar SMS via API — em seguida, o webhook fecha o ciclo com o status de cada mensagem. Para fluxos de verificação, veja como enviar OTP por SMS. Crie sua conta na SMSGo: R$ 10 de créditos grátis no cadastro, sem cartão, e webhooks assinados desde a primeira mensagem.