Webhooks
Receba eventos do PEPINO ZAP no seu endpoint em tempo real. Cadastre webhooks self-service via POST /v1/webhooks, com entrega assinada (HMAC), secret whsec_ exibido uma vez e política de retry.
Um webhook é uma notificação que o PEPINO ZAP envia ao seu servidor quando algo acontece em uma instância — uma mensagem chega, o status de entrega de uma mensagem enviada muda, ou a conexão da instância muda de estado. Em vez de o seu sistema ficar consultando a API repetidamente para saber "chegou algo novo?", o seu endpoint recebe um POST assinado a cada evento, no momento em que ele acontece.
A URL do webhook é a sua — o PEPINO ZAP não gera nenhuma URL. Aqui o modelo é o de
Stripe/Twilio, não o de Slack/Discord. No Slack/Discord você cria um "incoming webhook" e recebe
uma URL pronta para postar; aqui é o inverso: você expõe um endpoint público no seu
servidor (ex.: https://seusistema.com/webhook/pepinozap) e cadastra a URL dele no campo url; o
PEPINO ZAP faz POST nesse endereço a cada evento. Não existe nenhuma URL gerada por nós para
você consumir — quem fornece a URL é você.
Esta página é a visão geral: o que é um webhook, como cadastrá-lo via API (self-service), o que cada chamada retorna, os erros possíveis e quando preferir webhook ou WebSocket. O detalhe de cada parte vive nas páginas de Eventos, Assinatura e Entrega e retry.
Todas as requisições de gerenciamento (cadastrar, listar, remover) exigem o header Authorization: Bearer <API_KEY>. Os exemplos usam a URL de produção https://api.pepinozap.com.br com uma key pzk_live_; no modo teste, use https://sandbox.pepinozap.com.br com uma key pzk_test_. O host decide o modo, e a key precisa casar com o host — pzk_live_ em api.*, pzk_test_ em sandbox.*; usar a key no host errado retorna 401. Ver Autenticação.
Como funciona, em três passos
- Uma instância gera um evento —
message.received(mensagem recebida),message.status(entregue/lida) ouinstance.status(conexão mudou). - A plataforma envia um
POSTassinado para a URL que você cadastrou, com o corpo do evento e os headers de identificação e assinatura. - O seu backend lê o corpo, valida a assinatura e responde com
2xxpara confirmar o recebimento. Se você não responder2xx(ou demorar mais que o timeout), a entrega é reenviada.
Cada entrega traz três headers, sempre:
| Header | Conteúdo |
|---|---|
X-Pepino-Event | Nome do evento da entrega (ex.: message.received). |
X-Pepino-Webhook-Id | Identificador do webhook cadastrado (a configuração, ex.: wh_abc123). É constante em todas as entregas daquele mesmo webhook — não é um id por entrega nem um número de sequência. Para deduplicar (o mesmo evento pode chegar mais de uma vez), use um identificador de negócio presente em data, como waMessageId. |
X-Pepino-Signature | sha256=<hmac-hex> — o HMAC-SHA256 do corpo bruto da requisição, calculado com o secret do webhook. |
A assinatura é o que prova que a entrega veio do PEPINO ZAP (e não foi adulterada). Validá-la é fortemente recomendado antes de processar qualquer entrega — veja Assinatura.
O envelope de cada entrega
Independentemente do evento, o corpo do POST tem sempre o mesmo formato de envelope. O que muda entre os eventos é só o conteúdo de data:
{
"event": "message.received",
"instanceId": "cm5x8a9k20001abc123def456",
"timestamp": "2026-05-30T12:00:00Z",
"data": {}
}event— o nome do evento (message.received,message.statusouinstance.status).instanceId— a instância que originou o evento. Um webhook é escopado ao Projeto, então recebe eventos de todas as instâncias do Projeto; use este campo para saber de qual.timestamp— data e hora do evento em ISO 8601 (UTC).data— o objeto específico do evento. O formato de cada um está em Eventos.
Cadastrando um webhook
Você cadastra seus webhooks via API, de forma self-service, escopados ao seu Projeto — não depende do operador. Faça um POST /v1/webhooks com três campos:
curl -X POST https://api.pepinozap.com.br/v1/webhooks \
-H "Authorization: Bearer $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "meu-endpoint",
"url": "https://meusistema.com/webhook/pepinozap",
"events": []
}'O corpo da requisição
| Campo | Tipo | Obrigatório | O que é |
|---|---|---|---|
name | string | Sim | Um rótulo livre para você identificar o webhook (ex.: producao-crm). Espaços nas pontas são removidos; vazio é rejeitado com 400. Nomes podem repetir — não há checagem de duplicidade. |
url | string | Sim | A URL pública do seu endpoint que vai receber os POST. Precisa ser http/https absoluta. Recomenda-se https. |
events | array de strings | Não | Quais eventos este webhook deve receber. Array vazio ([]) ou omitido = todos os eventos. Para filtrar, liste só os que quer (ex.: ["message.received"]). |
Os valores aceitos em events são estes:
| Valor | Quando dispara |
|---|---|
message.received | Uma mensagem chegou em uma instância. |
message.status | Mudou o status de entrega de uma mensagem enviada (entregue/lida). |
instance.status | A conexão de uma instância mudou (conectou, caiu, deslogou, foi banida). |
call.received | Uma chamada (voz/vídeo) foi recebida. |
message.deleted | O remetente apagou/revogou uma mensagem. |
group.participants | Alguém entrou/saiu ou foi promovido/rebaixado em um grupo. |
group.updated | Nome ou descrição de um grupo mudou. |
contact.updated | O nome de exibição de um contato mudou. |
label.updated | Uma etiqueta (Business) foi criada, editada ou apagada. |
label.association | Uma etiqueta foi aplicada ou removida de uma conversa. |
Qualquer string fora dessa lista faz a chamada falhar com 400 e a mensagem evento inválido: <valor>. O payload dos principais está em Eventos.
A url é validada na criação: precisa ter esquema http ou https e ter um host. Se você
apontar para um endereço IP interno/privado literal (loopback como 127.0.0.1, faixas
privadas como 10.x/192.168.x, link-local etc.), a criação é rejeitada com 400 — é uma
proteção anti-SSRF. Para testar localmente, exponha o endpoint com um túnel (por exemplo, um
serviço de tunelamento HTTP) e cadastre a URL pública dele, não localhost.
A resposta: 201 Created com o secret
A criação retorna 201 Created com o webhook recém-criado e o secret:
{
"id": "wh_abc123",
"name": "meu-endpoint",
"url": "https://meusistema.com/webhook/pepinozap",
"events": [],
"enabled": true,
"lastSuccessAt": null,
"lastFailureAt": null,
"lastFailureReason": null,
"createdAt": "2026-05-30T12:00:00Z",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}id— o identificador do webhook. Guarde-o: é o que você usa para remover o webhook depois (DELETE /v1/webhooks/{id}).enabled— webhooks nascem habilitados (true); já começam a receber entregas. Você pode pausar e retomar a entrega depois sem perder a config (ver Pausar e retomar).lastSuccessAt,lastFailureAt,lastFailureReason— telemetria da última entrega bem-sucedida e da última falha. Vêmnullenquanto não houve entrega; depois ajudam a diagnosticar um endpoint com problema (veja Entrega e retry).secret— a chave para validar a assinatura das entregas. Detalhado a seguir.
O secret whsec_ é mostrado uma única vez
O campo secret, com prefixo whsec_, só aparece nesta resposta de criação. Ele não volta a ser exibido em nenhuma outra chamada — GET /v1/webhooks não o retorna. É com esse valor que você recalcula o HMAC e confere o header X-Pepino-Signature de cada entrega (ver Assinatura).
Copie o secret agora e guarde-o em variável de ambiente ou cofre de segredos (fora do
código-fonte). Se você perder o secret, não há como recuperá-lo: a única saída é remover o
webhook e criar outro, o que gera um secret novo — e, a partir daí, você passa a validar as
assinaturas com o secret novo. Cada webhook tem o seu próprio secret.
Variações de cadastro
Recebendo só um tipo de evento. Liste apenas o que interessa em events:
curl -X POST https://api.pepinozap.com.br/v1/webhooks \
-H "Authorization: Bearer $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "so-recebidas", "url": "https://meusistema.com/in", "events": ["message.received"] }'Vários endpoints. Você pode cadastrar mais de um webhook no mesmo Projeto — por exemplo, um endpoint para mensagens e outro para status de conexão. Cada um tem URL, eventos e secret próprios, e cada um recebe sua própria entrega assinada.
Listando seus webhooks
Use GET /v1/webhooks para ver os webhooks cadastrados no Projeto:
curl https://api.pepinozap.com.br/v1/webhooks \
-H "Authorization: Bearer $YOUR_API_KEY"A resposta é 200 OK com um array em items:
{
"items": [
{
"id": "wh_abc123",
"name": "meu-endpoint",
"url": "https://meusistema.com/webhook/pepinozap",
"events": [],
"enabled": true,
"lastSuccessAt": "2026-06-05T11:59:00Z",
"lastFailureAt": null,
"lastFailureReason": null,
"createdAt": "2026-05-30T12:00:00Z"
}
]
}Repare que a listagem não inclui o secret — esse campo só existe na resposta de criação. Use
lastSuccessAt / lastFailureAt / lastFailureReason para auditar a saúde de cada endpoint: um
lastFailureReason preenchido e um lastSuccessAt antigo indicam que o seu endpoint vem
rejeitando ou não respondendo às entregas.
A listagem respeita o modo da key: uma key pzk_test_ (host sandbox.*) lista os webhooks de teste; uma key pzk_live_ (host api.*) lista os de produção. São conjuntos separados.
Removendo um webhook
Para parar de receber entregas em um endpoint, remova o webhook pelo id retornado na criação (ou obtido na listagem):
curl -X DELETE https://api.pepinozap.com.br/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer $YOUR_API_KEY"Em sucesso, retorna 204 No Content (sem corpo). Se o id não existir, não for do seu Projeto, ou for de outro modo (teste vs. produção), retorna 404 com webhook não encontrado neste projeto.
A remoção é definitiva e invalida o secret daquele webhook. Para apenas interromper temporariamente as entregas (sem perder a config), use o pause abaixo em vez de remover. Já para trocar URL ou eventos não há edição: o fluxo é remover e criar de novo com os novos parâmetros (o que gera um secret novo) — atualize a configuração do seu endpoint antes de descartar o antigo.
Pausar e retomar
Às vezes você precisa parar de receber as entregas por um tempo — manutenção do seu endpoint, um deploy, ou investigar um problema — sem perder a configuração (URL, secret e eventos). Para isso, alterne o campo enabled com PATCH /v1/webhooks/{id}:
# pausar a entrega
curl -X PATCH https://api.pepinozap.com.br/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'
# retomar
curl -X PATCH https://api.pepinozap.com.br/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'Retorna 200 OK com o webhook atualizado. Enquanto enabled for false, nenhuma entrega é feita para aquele endpoint (os eventos nem chegam a ser enfileirados para ele); ao voltar para true, as entregas recomeçam a partir dos próximos eventos — o que ocorreu durante a pausa não é entregue retroativamente.
A vantagem sobre remover: pausar mantém o mesmo id e o mesmo secret — você religa
quando quiser, sem reconfigurar nada do seu lado nem trocar a chave de assinatura. Remover, ao
contrário, é definitivo e invalida o secret. No painel (Integrações → Webhooks), esse mesmo
controle é o botão Pausar / Retomar.
Entrega e idempotência (resumo)
A plataforma envia cada entrega via POST e espera uma resposta 2xx rápida. O comportamento completo está em Entrega e retry; o essencial:
- Timeout de 15 s por tentativa. Responda
2xxem poucos segundos e processe o evento de forma assíncrona. - Sucesso =
2xx. Qualquer4xx/5xx, timeout ou erro de rede conta como falha. - Retry com backoff exponencial, até 6 tentativas. Por isso, o mesmo evento pode chegar mais de uma vez: trate as entregas de forma idempotente, deduplicando por um identificador de negócio presente no
data(por exemplo,waMessageId). O headerX-Pepino-Webhook-Ididentifica o webhook cadastrado (é constante por webhook), então não serve sozinho para deduplicar eventos.
Erros possíveis no cadastro
O envelope de erro é { "error": { "code", "message" } }.
| Código | Significado |
|---|---|
400 | Corpo inválido. Casos: JSON malformado (JSON inválido: ...); name ausente (nome obrigatório); url inválida — esquema diferente de http/https, sem host, ou apontando para IP interno/privado (URL: ...); evento não suportado em events (evento inválido: <valor>). |
401 | API key ausente, inválida, ou usada no host do modo errado (pzk_live_ em sandbox.* ou vice-versa). Ver Autenticação. |
403 | Projeto inativo ou sem permissão. |
404 | (No DELETE) webhook não encontrado neste Projeto — id inexistente, de outro Projeto, ou de outro modo. |
500 | Erro interno ao gerar o secret ou ao persistir o webhook. |
Webhook ou WebSocket?
Os dois transportes entregam exatamente os mesmos eventos, com o mesmo envelope. A escolha é sobre a garantia de entrega que você precisa:
| Webhook | WebSocket | |
|---|---|---|
| Endpoint público | Necessário | Não precisa |
| Entrega | Com retry e backoff, até 6 tentativas | Só enquanto o cliente está conectado (sem buffer/retry) |
| Indicado para | Integrações de backend que exigem garantia de entrega | Frontends, dashboards, protótipos |
Se você não tem um endpoint público — por exemplo, num frontend ou protótipo — pode receber os mesmos eventos por uma conexão WebSocket, sem expor uma URL. O webhook continua sendo a opção recomendada quando você precisa de entrega garantida. Os dois podem ser usados ao mesmo tempo: o WebSocket não substitui o webhook quando você precisa de durabilidade.
Próximos passos
- Eventos — os três eventos disponíveis e o payload de cada um.
- Assinatura — como validar
X-Pepino-Signaturecom o secretwhsec_, com exemplos em Node.js, Python e Go. - Entrega e retry — a política de timeout, sucesso/falha e reenvio em detalhe.
- WebSocket — receber os eventos em tempo real sem endpoint público.
Disparo em massa (campanhas)
Como criar e disparar campanhas de mensagens em massa com cadência anti-ban — warmup, janela horária, ritmo com jitter, auto-pausa e validação de número — e acompanhar o resultado por destinatário via API e webhook.
Eventos disponíveis
Catálogo dos eventos de webhook do PEPINO ZAP, com o envelope comum e o payload de cada um, campo a campo.