Guias do produto
Webhooks de mensagens recebidas (listeners)
Um listener conecta um dos seus números de WhatsApp ao Unibox e pode enviar um webhook de mensagem recebida para o seu servidor. No painel, os listeners são gerenciados na página Gatilhos de Entrada. Quando um número vira listener, os chats dele são sincronizados com a caixa de entrada do Unibox e, se você adicionar uma URL de webhook, o Wbiztool envia cada mensagem nova para o seu servidor. Use webhooks para registrar conversas em um CRM, alertar sua equipe ou criar uma resposta automática.
Antes de começar#
- O add-on Unibox. Ele custa US$ 20/mês ou US$ 200/ano por número de WhatsApp, e a quantidade de listeners que você pode ter é igual à quantidade do add-on. Compre-o em Add-ons Disponíveis em Cobrança e Planos, dentro do painel. Sem ele, a página Gatilhos de Entrada ainda abre, mas clicar em Adicionar Novo Ouvinte mostra Unibox Add-on Required (add-on Unibox necessário) com um botão Subscribe to Unibox Add-on (assinar o add-on Unibox).
- Um número de WhatsApp conectado nas configurações do WhatsApp. Só podem ser adicionados números conectados que ainda não são listeners.
- Você precisa ser proprietário ou editor do espaço de trabalho.
- Para webhooks: uma URL pública (use
https) com 100 caracteres ou menos que aceite requisiçõesPOSTcom corpo JSON.
Adicionar um listener#
Abra Gatilhos de Entrada
Na barra lateral, abra Unibox e clique em Gatilhos de Entrada, ou acesse Gatilhos de Entrada.
Inicie um novo listener
Clique no card Adicionar Novo Ouvinte.
Escolha o número
Escolha-o em Selecionar Número do WhatsApp. Se a lista disser Nenhum número do WhatsApp disponível, todos os números conectados já são listeners, ou nenhum está conectado.
Adicione um webhook (opcional)
Digite sua URL do Webhook (Opcional). Assim que você digitar uma URL, aparece Webhook Events (eventos do webhook): mantenha marcado Incoming Messages (mensagens recebidas), Outgoing Messages (mensagens enviadas) ou os dois. Você pode adicionar ou alterar a URL depois.
Salve
Clique em Adicionar Listener. O listener aparece como um card com o status Active (ativo). Se você informou uma URL, um segredo de webhook é criado para ela.
Gerenciar listeners#
Cada card mostra o número, o status, a URL do Webhook: (ou Não configurado), a Última Atividade: (quando o número foi verificado pela última vez em busca de mensagens, ou Nunca) e o Segredo do Webhook:, oculto até você clicar no botão com o ícone de olho.
Abra o menu ⋮ de um card para:
| Ação | O que acontece |
|---|---|
| Editar | Altera a URL do webhook, os eventos do webhook ou o segredo. O número não pode ser alterado. |
| Desativar / Ativar | Desativar deixa o listener como Inactive (inativo): o número para de sincronizar com a caixa de entrada e nenhum webhook é enviado. Ativar deixa o listener Active de novo. |
| Excluir | Remove o listener depois que você confirma. As conversas que já estão na caixa de entrada continuam lá. Adicionar o mesmo número de novo mais tarde restaura o listener. Se você informar uma URL de webhook ao adicioná-lo de novo, o listener mantém o segredo de webhook anterior, se tinha um, em vez de receber um novo. |
Status dos listeners#
| Status | Significado |
|---|---|
| Active (ativo) | As mensagens são sincronizadas e os webhooks são enviados enquanto o número estiver conectado. |
| Pending (pendente) | O número não estava conectado quando o listener foi criado, por exemplo pelo Zapier. Clique em Ativar quando o número estiver conectado. |
| Inactive (inativo) | Desativado. Nada é sincronizado e nenhum webhook é enviado. |
Estatísticas#
| Card | O que mostra |
|---|---|
| Ouvintes Ativos | Todos os listeners da página, incluindo os desativados. |
| Números Disponíveis | Números conectados que ainda não são listeners. Números cujo listener você excluiu continuam contando como usados aqui; por isso, o valor pode ser menor do que a quantidade que você realmente pode adicionar. |
| Limite Total | Quantos listeners o seu add-on Unibox permite. |
| Mensagens Hoje | Ainda não é contabilizado; sempre mostra 0. |
Alterar a URL ou os eventos do webhook#
Abra o listener
Clique em ⋮ no card e depois em Editar.
Atualize as configurações
Altere a Webhook URL (Optional) (URL do webhook, opcional) e os Webhook Events. Apague a URL para parar os webhooks deste número: o segredo dele também é removido, e um novo é criado se você adicionar uma URL de novo.
Salve
Clique em Update Listener (atualizar listener).
Gerar um novo segredo#
Em Edit Listener (editar listener), clique no botão de atualizar ao lado de Webhook Secret (segredo do webhook) e confirme. O novo segredo é salvo imediatamente, mesmo que você feche a janela sem clicar em Update Listener, e a partir daí as requisições são assinadas com ele. Atualize seu servidor com o novo segredo imediatamente.
Como os webhooks são entregues#
O Wbiztool envia uma requisição POST para a sua URL para cada mensagem nova encontrada quando o número sincroniza, o que acontece a cada poucos minutos enquanto o número está conectado e não está ocupado enviando mensagens.
- Eventos:
message_receivedpara mensagens que as pessoas enviam para o seu número emessage_sentpara mensagens enviadas por ele (pelo celular, por campanhas ou pela API). Só os eventos marcados em Webhook Events são enviados. - Respostas enviadas pela caixa de entrada do Unibox normalmente não disparam
message_sent, porque a caixa de entrada já as tem quando a sincronização é executada. - Resposta: responda com HTTP
200em até 8 segundos. Qualquer outra resposta ou um timeout conta como falha na entrega. - Sem novas tentativas: cada mensagem é enviada uma única vez. Se o seu servidor estiver fora do ar, aquele webhook é perdido.
- Ordem: as requisições são enviadas de forma independente e podem chegar fora de ordem. Ordene por
message.timestampse a ordem for importante.
Cabeçalhos#
| Cabeçalho | Valor |
|---|---|
Content-Type | application/json |
X-Wbiztool-Event | message_received ou message_sent |
X-Wbiztool-Timestamp | Quando o webhook foi enviado, em ISO 8601 UTC. Igual a timestamp no corpo. |
X-Wbiztool-Webhook-Id | O ID do listener. Igual a webhook_id no corpo. |
X-Wbiztool-Signature | sha256= seguido da assinatura. É enviado sempre que o listener tem um segredo, o que sempre acontece quando há uma URL definida. |
Payload#
{
"event": "message_received",
"timestamp": "2026-09-16T10:31:12.482913+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0C1A2B3D4E5F60718",
"type": "chat",
"content": "Hi, is my order #4821 out for delivery?",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:29:58+00:00",
"whatsapp_timestamp": 1789554598,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_sent",
"timestamp": "2026-09-16T10:33:40.117205+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0F9E8D7C6B5A40312",
"type": "chat",
"content": "Yes, it will reach you today by 6 PM.",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:31:04+00:00",
"whatsapp_timestamp": 1789554664,
"direction": "outgoing",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:36:02.904311+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0A7B6C5D4E3F20109",
"type": "image",
"content": "📸 Image",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:34:51+00:00",
"whatsapp_timestamp": 1789554891,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0,
"media": {
"filename": "",
"mimetype": "image/jpeg",
"size": 245760
}
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:40:15.330187+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected][email protected]",
"type": "chat",
"content": "Is the store open on Sunday?",
"from": "120363041234567890",
"from_name": "Acme Loyalty Club",
"to": "919812345678",
"timestamp": "2026-09-16T10:39:02+00:00",
"whatsapp_timestamp": 1789555142,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "120363041234567890",
"name": "Acme Loyalty Club",
"is_group": true,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
},
"group": {
"name": "Acme Loyalty Club"
}
}Os números, IDs e nomes acima são exemplos.
Campos de nível superior#
| Campo | Tipo | Descrição |
|---|---|---|
event | string | message_received ou message_sent. |
timestamp | string | Quando o webhook foi enviado (ISO 8601, UTC). |
webhook_id | integer | O ID do listener. |
whatsapp_client_id | string | ID do seu número de WhatsApp, como aparece nas configurações do WhatsApp. |
whatsapp_phone | string | Seu número de WhatsApp. |
message | object | A mensagem. Veja abaixo. |
contact | object | A pessoa ou o grupo com quem é a conversa. Veja abaixo. |
organisation | object | id (string) e name do seu espaço de trabalho. |
group | object | Só em chats de grupo: name, o nome do grupo. |
Campos de message#
| Campo | Tipo | Descrição |
|---|---|---|
id | string | O ID da mensagem no WhatsApp. Use-o para ignorar duplicatas. |
type | string | chat para texto. Nos demais casos, o tipo do WhatsApp, como image, video, audio, ptt (mensagem de voz), document, sticker ou location. |
content | string | O texto, em mensagens chat. Para mídia, um rótulo no lugar do texto: 📸 Image, 🎥 Video, 🎵 Audio, 🎤 Voice Message, 😊 Sticker, Document: seguido do texto do documento (Document quando não há texto), ou o nome do tipo com iniciais maiúsculas para qualquer outra coisa, como Location. As legendas não são incluídas. |
from | string | Sempre o número do contato (ou o ID do grupo), nas duas direções. |
from_name | string | O nome do contato ou do grupo. Pode estar vazio. |
to | string | Sempre o seu número de WhatsApp, nas duas direções. |
timestamp | string | Quando a mensagem foi enviada no WhatsApp (ISO 8601, UTC). |
whatsapp_timestamp | integer | O mesmo horário como timestamp Unix em segundos. |
direction | string | incoming ou outgoing. Use este campo, e não from e to, para saber a direção. |
status | string | No momento é sempre pending. Não dependa dele para status de entrega ou de leitura. |
is_forwarded | boolean | Se a mensagem foi encaminhada. |
forwarding_score | integer | Quantas vezes ela foi encaminhada. |
media | object | Em mensagens de mídia, quando os detalhes estão disponíveis: filename, mimetype e size em bytes. O arquivo em si não é incluído. |
quoted_message_id | string | Só quando a mensagem responde a outra mensagem. |
Campos de contact#
| Campo | Tipo | Descrição |
|---|---|---|
whatsapp_id | string | O ID do WhatsApp, como [email protected] para uma pessoa ou …@g.us para um grupo. |
phone | string | O número sem +, ou o ID do grupo, no caso de grupos. |
name | string | O nome que o Wbiztool tem para o contato, ou o nome do grupo. Pode estar vazio. |
is_group | boolean | true para chats de grupo. |
is_business | boolean | true para contas do WhatsApp Business, quando isso é conhecido. |
Verificar a assinatura#
Cada requisição é assinada com o segredo do seu listener usando HMAC-SHA256. A assinatura é calculada sobre o corpo bruto da requisição, exatamente como foi recebido, e enviada como sha256= mais o digest hexadecimal em minúsculas no cabeçalho X-Wbiztool-Signature.
Sempre calcule a assinatura a partir dos bytes brutos, antes de fazer o parse do JSON. Fazer o parse e codificar o corpo de novo o altera (por exemplo, caracteres não ingleses e emojis chegam escapados como \uXXXX), e a assinatura não vai corresponder.
// Express: keep the raw body for this route
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.WBIZTOOL_WEBHOOK_SECRET;
app.post("/wbiztool/webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const received = req.get("X-Wbiztool-Signature") || "";
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.status(401).send("Invalid signature");
const data = JSON.parse(req.body.toString("utf8"));
if (data.event === "message_received") {
console.log(`New message from ${data.contact.phone}: ${data.message.content}`);
}
res.sendStatus(200); // reply quickly; do slow work in the background
});
app.listen(3000);# Flask
import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WBIZTOOL_WEBHOOK_SECRET"].encode()
@app.post("/wbiztool/webhook")
def wbiztool_webhook():
raw_body = request.get_data() # raw bytes, before parsing
expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
received = request.headers.get("X-Wbiztool-Signature", "")
if not hmac.compare_digest(expected, received):
abort(401)
data = json.loads(raw_body)
if data["event"] == "message_received":
print(f"New message from {data['contact']['phone']}: {data['message']['content']}")
return "", 200<?php
$secret = getenv('WBIZTOOL_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
$received = $_SERVER['HTTP_X_WBIZTOOL_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($rawBody, true);
if ($data['event'] === 'message_received') {
error_log('New message from ' . $data['contact']['phone'] . ': ' . $data['message']['content']);
}
http_response_code(200);A assinatura não cobre o cabeçalho de timestamp, então ela não protege contra uma requisição reenviada (replay). Se isso for importante, armazene cada message.id que você já processou e ignore as repetições.
Solução de problemas#
| Mensagem ou problema | O que fazer |
|---|---|
| Unibox Add-on Required ao clicar em Adicionar Novo Ouvinte | Seu espaço de trabalho não tem o add-on Unibox. Compre-o em Add-ons Disponíveis em Cobrança e Planos, dentro do painel. |
You have reached your unibox numbers limit | Exclua um listener de que não precisa mais ou aumente a quantidade do add-on. |
| Nenhum número do WhatsApp disponível | Conecte outro número, ou ele já é um listener. |
This WhatsApp number is already a listener | Edite o card existente. |
Invalid WhatsApp client | O número desconectou. Reconecte-o nas configurações do WhatsApp e recarregue a página. |
Erro mencionando value too long ao salvar | A URL do webhook tem mais de 100 caracteres. Use uma URL mais curta. |
| Nenhum webhook chega | Confira se o listener está Active, se o número está conectado, se o tipo de evento está marcado e se a sua URL é https pública com certificado válido. As mensagens só são enviadas depois da próxima sincronização, alguns minutos depois. |
| Alguns webhooks estão faltando | Seu servidor retornou algo diferente de 200, demorou mais de 8 segundos ou estava inacessível. Entregas com falha não são reenviadas. |
| A assinatura não corresponde | Use o corpo bruto, não o JSON codificado de novo, e o segredo atual. Gerar um novo segredo, ou conectar o Zapier, substitui o segredo. |
| Última Atividade: diz Nunca | O número ainda não foi verificado. Ele precisa estar conectado e não pode estar ocupado enviando mensagens. |
