Webhooks
Receba os eventos do workspace no seu servidor, assinados no padrão Standard Webhooks.
Um endpoint de webhook recebe um POST a cada evento que ele assina. Crie endpoints em Configurações → Desenvolvedores → Webhooks (Owner ou Admin) ou com POST /v1/webhooks:
curl https://app.zaplin.com.br/api/v1/webhooks \
-X POST \
-H "Authorization: Bearer $ZAPLIN_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Meu CRM", "url": "https://seuapp.com/zaplin", "events": ["message.received", "stage.changed"]}'{ "id": "…", "secret": "whsec_…" }O secret assina as entregas e só aparece uma vez. Não há como vê-lo depois: se você o perder, rotacione a chave para receber outra.
O endereço tem que ser HTTPS e público: sem usuário e senha, sem IP, sem localhost e sem domínios .local, .internal ou .localhost.
Eventos
| Evento | Quando |
|---|---|
message.received | Chega uma mensagem de um contato (não de grupo) |
message.sent | Uma mensagem para um contato sai com sucesso, de uma pessoa, automação, campanha, lembrete ou da API |
conversation.closed | Uma conversa é fechada na caixa de entrada ou por uma automação |
contact.created | Um contato é criado: à mão, pela API, por formulário, importação, primeira mensagem ou automação |
stage.changed | Um negócio muda de etapa no pipeline, por uma pessoa ou por uma automação |
automation.failed | Uma execução de automação termina com falha |
A entrega
Toda entrega segue a especificação Standard Webhooks: um POST com Content-Type: application/json e três cabeçalhos.
POST /zaplin HTTP/1.1
content-type: application/json
webhook-id: evt_3f9a1c2b7d4e5f60
webhook-timestamp: 1792242191
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=| Cabeçalho | O que é |
|---|---|
webhook-id | O id do evento, evt_…: o mesmo em todas as tentativas e reenvios, igual ao id do corpo |
webhook-timestamp | Quando esta tentativa saiu, em segundos Unix; muda a cada tentativa |
webhook-signature | Uma ou mais v1,<assinatura>, separadas por espaço. Veja a verificação |
{
"id": "evt_3f9a1c2b7d4e5f60",
"type": "message.received",
"version": 1,
"timestamp": "2026-10-17T14:03:11.512Z",
"organization_id": "…",
"data": {
"message": { "id": "…", "text": "Oi, quero saber o preço" },
"contact": { "name": "Maria Silva", "phone": "+5511987654321" },
"number": { "id": "…" },
"contact_id": "…",
"conversation_id": "…",
"number_id": "…"
}
}| Campo | O que é |
|---|---|
id | O id do evento, igual ao webhook-id |
type | O evento, da tabela acima |
version | A versão do envelope: 1 |
timestamp | Quando o evento aconteceu, em ISO 8601 e UTC |
organization_id | O workspace |
data | O que é próprio do evento. Sempre traz contact_id, conversation_id e number_id, que são null quando o evento não tem esse dado |
O data de cada evento
source diz quem enviou: member, automation, campaign, reminder, api, system ou phone.
{
"message": { "id": "…", "text": "Olá!" },
"contact": { "name": "Maria Silva", "phone": "+5511987654321" },
"number": { "id": "…" },
"source": "member",
"contact_id": "…",
"conversation_id": "…",
"number_id": "…"
}Resposta e novas tentativas
Responda com qualquer 2xx em até 10 segundos. O corpo da sua resposta é ignorado; guardamos os primeiros 500 caracteres para você ver na página do endpoint. Redirecionamentos não são seguidos: contam como falha.
Qualquer outra resposta, ou nenhuma, é uma falha, e a entrega é tentada de novo com espera exponencial a partir de 30 segundos (30 s, 1 min, 2 min, 4 min…): até 12 tentativas, dentro de 24 horas. Depois disso, a entrega fica failed. Se o endereço resolver para um IP privado, ou o DNS falhar, a entrega fica blocked e não é tentada de novo. O endpoint nunca é desligado por falhas.
- Idempotência: o
webhook-id(oiddo corpo) é o mesmo em todas as tentativas e reenvios de um evento. Guarde os ids que você já processou e ignore as repetições. - Ordem: as entregas podem chegar fora de ordem. Use o
timestampe leia o estado atual pela API quando a ordem importar. - Pausa: Pausar entregas, na página do endpoint, guarda os eventos que acontecem nesse meio-tempo; Retomar entregas envia tudo o que ficou guardado.
- Reenvio: na página do endpoint você vê as últimas 50 entregas (status, código, duração, tentativas e a próxima). Reativar envia uma entrega de novo e Reenviar falhas todas as que ainda falham, na hora, com o mesmo
ide corpo. O histórico fica guardado por 30 dias.
Verifique a assinatura
Cada assinatura em webhook-signature é v1, seguido do HMAC-SHA256 em base64 de {webhook-id}.{webhook-timestamp}.{corpo}, sobre o corpo bruto. A chave do HMAC é a parte do segredo depois de whsec_, decodificada de base64. Durante uma rotação chegam duas assinaturas: aceite a entrega quando qualquer uma bater. Recuse um webhook-timestamp com mais de 5 minutos de diferença do seu relógio, contra replays.
As bibliotecas oficiais do Standard Webhooks fazem tudo isso:
// npm install standardwebhooks
import { Webhook } from "standardwebhooks"
const webhook = new Webhook(process.env.ZAPLIN_WEBHOOK_SECRET!) // "whsec_…"
// Um route handler do Next.js: leia o corpo como texto, antes de qualquer parse.
export async function POST(request: Request) {
const body = await request.text()
let event
try {
// Confere as assinaturas e a janela de 5 minutos; devolve o corpo já lido.
event = webhook.verify(body, Object.fromEntries(request.headers))
} catch {
return new Response(null, { status: 401 })
}
// ... trate event.type e event.data, ignorando ids já vistos
return new Response(null, { status: 204 })
}Use o corpo bruto
Calcule o HMAC sobre os bytes recebidos, antes de qualquer parse. Um JSON serializado de novo não bate com a assinatura.
Rotacionar a chave
Na página do endpoint, Rotacionar chave gera uma chave nova e a mostra uma vez. A chave anterior continua assinando por 24 horas: nesse tempo o webhook-signature traz duas assinaturas, a da chave nova primeiro, e o seu sistema aceita a entrega com qualquer uma das chaves. Depois, só a chave nova assina.
- Clique em Rotacionar chave e copie a chave nova.
- Troque a chave no seu sistema e publique, dentro de 24 horas.
- Pronto: depois de 24 horas a chave anterior deixa de assinar.
Perdeu a chave? Rotacione para receber outra.
Limites
O plano limita os endpoints de um workspace: 2 no Essencial, 10 no Profissional e 30 no Escala. Acima disso, POST /v1/webhooks recebe 409 com quota.