{"id":43,"date":"2026-10-20T19:07:40","date_gmt":"2026-10-20T19:07:40","guid":{"rendered":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/"},"modified":"2026-10-20T19:07:40","modified_gmt":"2026-10-20T19:07:40","slug":"webhook-whatsapp-api","status":"publish","type":"post","link":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/","title":{"rendered":"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos"},"content":{"rendered":"\n<p><em>Atualizado em 20\/10\/2026<\/em><\/p>\n\n\n\n<p>O webhook da API do WhatsApp \u00e9 uma URL HTTPS do seu sistema que recebe, via <code>POST<\/code>, cada evento da conta: mensagens recebidas, mudan\u00e7as de status (<code>sent<\/code>, <code>delivered<\/code>, <code>read<\/code>, <code>failed<\/code>) e alertas da conta. Um endpoint bem feito valida a origem da requisi\u00e7\u00e3o, responde 200 em poucos segundos, coloca o evento numa fila e processa em segundo plano, ignorando duplicados pelo ID. Na integra\u00e7\u00e3o direta com a Meta h\u00e1 tamb\u00e9m um passo de verifica\u00e7\u00e3o por <code>GET<\/code>; na Vectalk, a URL \u00e9 registrada por API e recebe o mesmo payload da Meta.<\/p>\n\n\n\n<p>Este guia mostra o formato dos eventos, as diferen\u00e7as entre integrar direto e pela Vectalk, e um servidor completo em Node.js e em Python.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Como o fluxo de eventos funciona<\/h2>\n\n\n\n<p>Na integra\u00e7\u00e3o direta, a Meta chama o endpoint configurado no seu app. Com a Vectalk, a Meta chama a Vectalk, que repassa o evento para todas as URLs ativas registradas para aquela inst\u00e2ncia:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Direto:   Meta  \u2500\u2500POST\u2500\u2500\u25b6  seu endpoint\nVectalk:  Meta  \u2500\u2500POST\u2500\u2500\u25b6  Vectalk  \u2500\u2500POST\u2500\u2500\u25b6  suas URLs (uma ou v\u00e1rias)<\/code><\/pre>\n\n\n\n<p>O corpo \u00e9 o mesmo nos dois casos. O que muda \u00e9 como a URL \u00e9 cadastrada e como a requisi\u00e7\u00e3o \u00e9 autenticada.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Aspecto<\/th><th>Cloud API direta<\/th><th>Vectalk<\/th><\/tr><\/thead><tbody><tr><td>Cadastro da URL<\/td><td>Painel de Apps &gt; WhatsApp &gt; Configura\u00e7\u00e3o<\/td><td><code>POST \/api\/webhooks<\/code> com <code>instance_id<\/code>, <code>url<\/code> e <code>name<\/code><\/td><\/tr><tr><td>Verifica\u00e7\u00e3o do endpoint<\/td><td><code>GET<\/code> com <code>hub.mode<\/code>, <code>hub.verify_token<\/code> e <code>hub.challenge<\/code><\/td><td>N\u00e3o descrita na documenta\u00e7\u00e3o da Vectalk<\/td><\/tr><tr><td>Assinatura<\/td><td>Header <code>X-Hub-Signature-256<\/code> (HMAC-SHA256 com o App Secret)<\/td><td>N\u00e3o descrita na documenta\u00e7\u00e3o da Vectalk<\/td><\/tr><tr><td>URLs por conta<\/td><td>Um endpoint por app (com substitui\u00e7\u00f5es por conta ou n\u00famero)<\/td><td>V\u00e1rias URLs por inst\u00e2ncia, todas recebem o mesmo evento<\/td><\/tr><tr><td>Tempo de resposta<\/td><td>A Meta reenvia se n\u00e3o receber 200<\/td><td>Responder 200 em menos de 20 segundos; sen\u00e3o a Vectalk reenvia<\/td><\/tr><tr><td>Novas tentativas<\/td><td>Frequ\u00eancia decrescente por at\u00e9 7 dias<\/td><td>Reenvio documentado, sem prazo informado<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Fontes: <a href=\"https:\/\/developers.facebook.com\/docs\/whatsapp\/cloud-api\/guides\/set-up-webhooks\">webhooks da Cloud API<\/a> (atualizada em 26\/06\/2026), <a href=\"https:\/\/developers.facebook.com\/docs\/graph-api\/webhooks\/getting-started\">primeiros passos com webhooks da Graph API<\/a> e <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">webhooks na documenta\u00e7\u00e3o da Vectalk<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Registrando o webhook na Vectalk<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">curl -X POST https:\/\/apis.vectalk.com.br\/api\/webhooks \\\n  -H \"Authorization: Bearer SEU_TOKEN\" \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\n    \"instance_id\": \"SEU_INSTANCE_ID\",\n    \"url\": \"https:\/\/meuapp.com.br\/whatsapp\/eventos\",\n    \"name\": \"Produ\u00e7\u00e3o\"\n  }'<\/code><\/pre>\n\n\n\n<p>A resposta traz o <code>id<\/code> do webhook e <code>active: true<\/code>. Com <code>PATCH<\/code> no mesmo recurso voc\u00ea troca a URL, o nome ou desativa o webhook; os endpoints est\u00e3o em <a href=\"https:\/\/docs.vectalk.com.br\/api-reference\/webhooks\/register\">Registrar webhook<\/a> e <a href=\"https:\/\/docs.vectalk.com.br\/api-reference\/webhooks\/update\">Atualizar webhook<\/a>. A URL precisa ser HTTPS.<\/p>\n\n\n\n<p>Ter v\u00e1rias URLs por inst\u00e2ncia ajuda em dois casos: separar produ\u00e7\u00e3o e homologa\u00e7\u00e3o e mandar o mesmo evento para dois sistemas (o CRM e o data warehouse, por exemplo) sem montar um repasse pr\u00f3prio.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verifica\u00e7\u00e3o do endpoint (integra\u00e7\u00e3o direta)<\/h2>\n\n\n\n<p>Quando voc\u00ea configura o webhook no painel da Meta, ela faz um <code>GET<\/code> na sua URL com tr\u00eas par\u00e2metros:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GET https:\/\/meuapp.com.br\/whatsapp\/eventos?hub.mode=subscribe&amp;hub.challenge=1158201444&amp;hub.verify_token=meu-token-secreto<\/code><\/pre>\n\n\n\n<p>O endpoint deve conferir se <code>hub.verify_token<\/code> \u00e9 igual ao valor que voc\u00ea definiu no painel e devolver o <code>hub.challenge<\/code> no corpo, com status 200. Qualquer outra resposta faz a verifica\u00e7\u00e3o falhar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Os payloads que voc\u00ea vai receber<\/h2>\n\n\n\n<p>Todo evento chega com <code>object: \"whatsapp_business_account\"<\/code> e uma lista <code>entry<\/code>. Cada <code>entry<\/code> tem uma lista <code>changes<\/code>, e cada <code>change<\/code> tem <code>field<\/code> e <code>value<\/code>. Para mensagens e status, <code>field<\/code> \u00e9 <code>messages<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Mensagem recebida<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-json\">{\n  \"object\": \"whatsapp_business_account\",\n  \"entry\": [{\n    \"id\": \"WABA_ID\",\n    \"changes\": [{\n      \"field\": \"messages\",\n      \"value\": {\n        \"messaging_product\": \"whatsapp\",\n        \"metadata\": {\n          \"display_phone_number\": \"558430000000\",\n          \"phone_number_id\": \"PHONE_NUMBER_ID\"\n        },\n        \"contacts\": [{ \"profile\": { \"name\": \"Jo\u00e3o Silva\" }, \"wa_id\": \"5584988888888\" }],\n        \"messages\": [{\n          \"from\": \"5584988888888\",\n          \"id\": \"wamid.HBgLNTUx...\",\n          \"timestamp\": \"1760000000\",\n          \"type\": \"text\",\n          \"text\": { \"body\": \"Ol\u00e1, quero mais informa\u00e7\u00f5es\" }\n        }]\n      }\n    }]\n  }]\n}<\/code><\/pre>\n\n\n\n<p>O campo <code>type<\/code> diz onde est\u00e1 o conte\u00fado. Os mais comuns:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th><code>type<\/code><\/th><th>Onde ler<\/th><\/tr><\/thead><tbody><tr><td><code>text<\/code><\/td><td><code>text.body<\/code><\/td><\/tr><tr><td><code>image<\/code>, <code>video<\/code>, <code>audio<\/code>, <code>document<\/code>, <code>sticker<\/code><\/td><td><code>&lt;tipo&gt;.id<\/code> (baixe pelo endpoint de m\u00eddia), <code>caption<\/code>, <code>filename<\/code><\/td><\/tr><tr><td><code>interactive<\/code><\/td><td><code>interactive.button_reply<\/code> ou <code>interactive.list_reply<\/code><\/td><\/tr><tr><td><code>location<\/code><\/td><td><code>location.latitude<\/code>, <code>location.longitude<\/code><\/td><\/tr><tr><td><code>reaction<\/code><\/td><td><code>reaction.message_id<\/code>, <code>reaction.emoji<\/code><\/td><\/tr><tr><td><code>order<\/code><\/td><td><code>order.catalog_id<\/code>, <code>order.product_items[]<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Status de mensagem enviada<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-json\">{\n  \"object\": \"whatsapp_business_account\",\n  \"entry\": [{\n    \"id\": \"WABA_ID\",\n    \"changes\": [{\n      \"field\": \"messages\",\n      \"value\": {\n        \"messaging_product\": \"whatsapp\",\n        \"metadata\": { \"display_phone_number\": \"558430000000\", \"phone_number_id\": \"PHONE_NUMBER_ID\" },\n        \"statuses\": [{\n          \"id\": \"wamid.HBgLNTUx...\",\n          \"status\": \"delivered\",\n          \"timestamp\": \"1760000010\",\n          \"recipient_id\": \"5584988888888\",\n          \"pricing\": { \"billable\": true, \"pricing_model\": \"PMP\", \"type\": \"regular\", \"category\": \"utility\" }\n        }]\n      }\n    }]\n  }]\n}<\/code><\/pre>\n\n\n\n<p>O <code>id<\/code> do status \u00e9 o <code>wamid<\/code> devolvido quando voc\u00ea enviou a mensagem. Quando <code>status<\/code> \u00e9 <code>failed<\/code>, o objeto traz <code>errors[]<\/code> com <code>code<\/code>, <code>title<\/code> e <code>message<\/code>. O objeto <code>pricing<\/code> indica categoria e se a mensagem \u00e9 cobr\u00e1vel; a refer\u00eancia completa est\u00e1 na <a href=\"https:\/\/developers.facebook.com\/docs\/whatsapp\/cloud-api\/webhooks\/reference\/messages\/status\">documenta\u00e7\u00e3o do webhook de status<\/a>. Para entender o que cada categoria custa, veja <a href=\"https:\/\/vectalk.com.br\/blog\/api-whatsapp-preco-brasil\/\">Quanto custa a API do WhatsApp no Brasil em 2026<\/a>.<\/p>\n\n\n\n<p>Um mesmo <code>POST<\/code> pode trazer v\u00e1rias mensagens ou v\u00e1rios status. Itere as listas em vez de ler s\u00f3 o primeiro item.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Boas pr\u00e1ticas<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Responda 200 r\u00e1pido e processe depois<\/h3>\n\n\n\n<p>O endpoint n\u00e3o deve chamar o seu banco, a IA ou outra API antes de responder. Grave o corpo bruto numa fila (Redis, SQS, RabbitMQ, uma tabela de eventos) e devolva 200. Um worker separado faz o trabalho pesado. A Vectalk documenta um limite de 20 segundos para a resposta; fique bem abaixo disso.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Seja idempotente<\/h3>\n\n\n\n<p>A Meta avisa que as novas tentativas podem duplicar notifica\u00e7\u00f5es, e um proxy tamb\u00e9m pode reenviar. Use o <code>id<\/code> da mensagem (ou a combina\u00e7\u00e3o <code>id<\/code> + <code>status<\/code>, no caso de status) como chave \u00fanica. Se a chave j\u00e1 foi processada, descarte.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Valide a assinatura<\/h3>\n\n\n\n<p>Na integra\u00e7\u00e3o direta, a Meta assina cada <code>POST<\/code> com HMAC-SHA256 do corpo, usando o App Secret do seu app, e envia o resultado no header <code>X-Hub-Signature-256<\/code> no formato <code>sha256=&lt;hash&gt;<\/code>. Calcule o hash sobre o <strong>corpo bruto<\/strong>, antes de qualquer parse de JSON, e compare em tempo constante. Com a Vectalk, siga o mecanismo de autentica\u00e7\u00e3o que a Vectalk indicar para as requisi\u00e7\u00f5es repassadas; enquanto isso n\u00e3o estiver definido no seu ambiente, use uma URL com caminho dif\u00edcil de adivinhar, restrinja o endpoint a HTTPS e n\u00e3o confie no conte\u00fado para a\u00e7\u00f5es sens\u00edveis sem checar o <code>phone_number_id<\/code> esperado.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">N\u00e3o dependa da ordem<\/h3>\n\n\n\n<p><code>delivered<\/code> pode chegar antes de <code>sent<\/code>, e <code>read<\/code> pode chegar sem <code>delivered<\/code>. Guarde o status mais avan\u00e7ado e o <code>timestamp<\/code> de cada um, sem regredir.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Planeje para payloads grandes<\/h3>\n\n\n\n<p>A Meta informa que os payloads de webhook podem ter at\u00e9 3 MB. Ajuste o limite de corpo do seu servidor (o padr\u00e3o do Express \u00e9 100 kb).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Monitore<\/h3>\n\n\n\n<p>Registre quantos eventos chegam por minuto e quantos falham no worker. Uma queda brusca de eventos de mensagens \u00e9, muitas vezes, o primeiro sinal de webhook desativado, certificado vencido ou n\u00famero desconectado.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Exemplo completo em Node.js (Express)<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-javascript\">import express from 'express'\nimport crypto from 'node:crypto'\nimport { createClient } from 'redis'\n\nconst app = express()\nconst redis = createClient({ url: process.env.REDIS_URL })\nawait redis.connect()\n\nconst VERIFY_TOKEN = process.env.WHATSAPP_VERIFY_TOKEN \/\/ integra\u00e7\u00e3o direta\nconst APP_SECRET = process.env.META_APP_SECRET        \/\/ integra\u00e7\u00e3o direta\n\n\/\/ Guarda o corpo bruto para validar a assinatura\napp.use('\/whatsapp\/eventos', express.raw({ type: 'application\/json', limit: '3mb' }))\n\n\/\/ Verifica\u00e7\u00e3o do endpoint (s\u00f3 na integra\u00e7\u00e3o direta com a Meta)\napp.get('\/whatsapp\/eventos', (req, res) =&gt; {\n  const { 'hub.mode': mode, 'hub.verify_token': token, 'hub.challenge': challenge } = req.query\n  if (mode === 'subscribe' &amp;&amp; token === VERIFY_TOKEN) return res.status(200).send(challenge)\n  return res.sendStatus(403)\n})\n\nfunction assinaturaValida(req) {\n  const header = req.get('X-Hub-Signature-256') || ''\n  const esperado = 'sha256=' + crypto.createHmac('sha256', APP_SECRET).update(req.body).digest('hex')\n  const a = Buffer.from(header)\n  const b = Buffer.from(esperado)\n  return a.length === b.length &amp;&amp; crypto.timingSafeEqual(a, b)\n}\n\napp.post('\/whatsapp\/eventos', async (req, res) =&gt; {\n  if (APP_SECRET &amp;&amp; !assinaturaValida(req)) return res.sendStatus(401)\n  await redis.lPush('whatsapp:eventos', req.body.toString('utf8'))\n  res.sendStatus(200)\n})\n\napp.listen(3000)<\/code><\/pre>\n\n\n\n<p>E o worker, que consome a fila e descarta duplicados:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-javascript\">async function processar(evento) {\n  for (const entry of evento.entry ?? []) {\n    for (const change of entry.changes ?? []) {\n      const v = change.value ?? {}\n      for (const msg of v.messages ?? []) {\n        const novo = await redis.set(`wa:msg:${msg.id}`, '1', { NX: true, EX: 60 * 60 * 24 * 8 })\n        if (!novo) continue \/\/ duplicado\n        await tratarMensagem(v.metadata.phone_number_id, msg) \/\/ sua regra de neg\u00f3cio\n      }\n      for (const st of v.statuses ?? []) {\n        const novo = await redis.set(`wa:st:${st.id}:${st.status}`, '1', { NX: true, EX: 60 * 60 * 24 * 8 })\n        if (!novo) continue\n        await atualizarStatus(st) \/\/ grava status, erros e pricing\n      }\n    }\n  }\n}\n\nwhile (true) {\n  const item = await redis.brPop('whatsapp:eventos', 0)\n  try {\n    await processar(JSON.parse(item.element))\n  } catch (err) {\n    console.error('falha ao processar evento', err)\n    await redis.lPush('whatsapp:eventos:erro', item.element)\n  }\n}<\/code><\/pre>\n\n\n\n<p>A expira\u00e7\u00e3o de oito dias nas chaves cobre a janela de novas tentativas da Meta, que vai at\u00e9 sete dias.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Exemplo completo em Python (FastAPI)<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">import hashlib, hmac, json, os\nimport redis.asyncio as redis\nfrom fastapi import FastAPI, Request, Response\n\napp = FastAPI()\nr = redis.from_url(os.environ[\"REDIS_URL\"])\nVERIFY_TOKEN = os.environ.get(\"WHATSAPP_VERIFY_TOKEN\")  # integra\u00e7\u00e3o direta\nAPP_SECRET = os.environ.get(\"META_APP_SECRET\", \"\")      # integra\u00e7\u00e3o direta\n\n\n@app.get(\"\/whatsapp\/eventos\")\nasync def verificar(request: Request):\n    q = request.query_params\n    if q.get(\"hub.mode\") == \"subscribe\" and q.get(\"hub.verify_token\") == VERIFY_TOKEN:\n        return Response(content=q.get(\"hub.challenge\", \"\"), media_type=\"text\/plain\")\n    return Response(status_code=403)\n\n\ndef assinatura_valida(corpo: bytes, header: str) -&gt; bool:\n    esperado = \"sha256=\" + hmac.new(APP_SECRET.encode(), corpo, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(esperado, header or \"\")\n\n\n@app.post(\"\/whatsapp\/eventos\")\nasync def receber(request: Request):\n    corpo = await request.body()\n    if APP_SECRET and not assinatura_valida(corpo, request.headers.get(\"X-Hub-Signature-256\")):\n        return Response(status_code=401)\n    await r.lpush(\"whatsapp:eventos\", corpo)\n    return Response(status_code=200)<\/code><\/pre>\n\n\n\n<p>Worker em Python:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">import asyncio, json, os\nimport redis.asyncio as redis\n\nr = redis.from_url(os.environ[\"REDIS_URL\"])\nOITO_DIAS = 60 * 60 * 24 * 8\n\n\nasync def processar(evento: dict):\n    for entry in evento.get(\"entry\", []):\n        for change in entry.get(\"changes\", []):\n            v = change.get(\"value\", {})\n            for msg in v.get(\"messages\", []):\n                if not await r.set(f\"wa:msg:{msg['id']}\", 1, nx=True, ex=OITO_DIAS):\n                    continue\n                await tratar_mensagem(v[\"metadata\"][\"phone_number_id\"], msg)\n            for st in v.get(\"statuses\", []):\n                if not await r.set(f\"wa:st:{st['id']}:{st['status']}\", 1, nx=True, ex=OITO_DIAS):\n                    continue\n                await atualizar_status(st)\n\n\nasync def main():\n    while True:\n        _, item = await r.brpop(\"whatsapp:eventos\")\n        try:\n            await processar(json.loads(item))\n        except Exception as exc:\n            print(\"falha ao processar evento\", exc)\n            await r.lpush(\"whatsapp:eventos:erro\", item)\n\nasyncio.run(main())<\/code><\/pre>\n\n\n\n<p>As fun\u00e7\u00f5es <code>tratar_mensagem<\/code> e <code>atualizar_status<\/code> (e as equivalentes em Node) s\u00e3o a sua regra de neg\u00f3cio: responder, gravar no CRM, acionar um agente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">O que fazer com cada tipo de evento no worker<\/h2>\n\n\n\n<p>Separar o tratamento por tipo de evento deixa o worker mais f\u00e1cil de manter. Uma divis\u00e3o que funciona bem:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><strong>Mensagens recebidas<\/strong>: grave a mensagem, atualize o hor\u00e1rio da \u00faltima mensagem do contato (\u00e9 ele que diz se a janela de 24 horas est\u00e1 aberta) e s\u00f3 ent\u00e3o acione o chatbot, o agente ou a fila de atendimento humano.<\/li>\n\n\n<li>**Status <code>sent<\/code>, <code>delivered<\/code> e <code>read<\/code>**: atualize o registro da mensagem enviada pelo <code>wamid<\/code>, guardando o hor\u00e1rio de cada etapa. Esses dados alimentam relat\u00f3rios de entrega e de leitura.<\/li>\n\n\n<li>**Status <code>failed<\/code>**: grave o c\u00f3digo de erro e decida a a\u00e7\u00e3o. Alguns erros pedem nova tentativa mais tarde; outros indicam que o envio n\u00e3o vai funcionar sem mudan\u00e7a, como um template fora da janela ou um n\u00famero inv\u00e1lido. N\u00e3o reenvie em la\u00e7o.<\/li>\n\n\n<li><strong>Outros campos<\/strong> (<code>account_update<\/code>, alertas da conta, mudan\u00e7as de template): direcione para um canal de alerta do time t\u00e9cnico, porque costumam exigir a\u00e7\u00e3o humana.<\/li>\n\n<\/ul>\n\n\n\n<p>Mantenha tamb\u00e9m uma fila de erros, como nos exemplos acima. Um evento que falhou no processamento n\u00e3o deve sumir: ele precisa ficar dispon\u00edvel para reprocessamento depois que o bug for corrigido.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testando localmente<\/h2>\n\n\n\n<p>Para receber eventos reais no computador, exponha a porta local com um t\u00fanel HTTPS (ngrok, Cloudflare Tunnel ou similar) e registre a URL do t\u00fanel como um webhook adicional da inst\u00e2ncia, com um nome como &#8220;dev&#8221;. Como a Vectalk entrega o evento para todas as URLs ativas, o ambiente de desenvolvimento recebe o mesmo tr\u00e1fego da produ\u00e7\u00e3o sem interferir nela. Desative o webhook de dev quando terminar.<\/p>\n\n\n\n<p>Tamb\u00e9m d\u00e1 para testar o parser sem tr\u00e1fego real, salvando payloads de exemplo e enviando com <code>curl<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">curl -X POST http:\/\/localhost:3000\/whatsapp\/eventos \\\n  -H \"Content-Type: application\/json\" \\\n  --data @exemplos\/mensagem-texto.json<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Erros comuns<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Sintoma<\/th><th>Causa prov\u00e1vel<\/th><\/tr><\/thead><tbody><tr><td>Mensagens processadas duas vezes<\/td><td>Falta de deduplica\u00e7\u00e3o por <code>id<\/code><\/td><\/tr><tr><td>Eventos chegando atrasados em rajada<\/td><td>Endpoint lento; a origem acumulou novas tentativas<\/td><\/tr><tr><td>Assinatura nunca bate<\/td><td>Hash calculado sobre o JSON j\u00e1 convertido, n\u00e3o sobre o corpo bruto<\/td><\/tr><tr><td><code>413 Payload Too Large<\/code><\/td><td>Limite de corpo do servidor abaixo de 3 MB<\/td><\/tr><tr><td>S\u00f3 a primeira mensagem do lote \u00e9 tratada<\/td><td>C\u00f3digo lendo <code>messages[0]<\/code> em vez de iterar<\/td><\/tr><tr><td>Status regredindo de <code>read<\/code> para <code>delivered<\/code><\/td><td>Eventos fora de ordem sobrescrevendo o estado<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Perguntas frequentes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Preciso responder alguma coisa no corpo do webhook?<\/h3>\n\n\n\n<p>N\u00e3o. Basta o status HTTP 200. O conte\u00fado da resposta \u00e9 ignorado, exceto na verifica\u00e7\u00e3o por <code>GET<\/code> da integra\u00e7\u00e3o direta, em que o corpo deve ser o <code>hub.challenge<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Posso usar a mesma URL para v\u00e1rios n\u00fameros?<\/h3>\n\n\n\n<p>Sim. Use <code>metadata.phone_number_id<\/code> para saber a qual n\u00famero o evento pertence. Na Vectalk, cada webhook \u00e9 registrado por inst\u00e2ncia, e voc\u00ea pode apontar v\u00e1rias inst\u00e2ncias para a mesma URL.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Por quanto tempo a Meta tenta reenviar um evento?<\/h3>\n\n\n\n<p>Por at\u00e9 sete dias, com frequ\u00eancia decrescente, segundo a documenta\u00e7\u00e3o de webhooks da Cloud API. Na Vectalk, a documenta\u00e7\u00e3o informa que o evento \u00e9 reenviado quando o endpoint n\u00e3o responde 200 em at\u00e9 20 segundos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Como recebo as mensagens que o dono responde pelo app, na coexist\u00eancia?<\/h3>\n\n\n\n<p>Pelo campo de webhook <code>smb_message_echoes<\/code>. O artigo <a href=\"https:\/\/vectalk.com.br\/blog\/coexistencia-whatsapp-api\/\">Coexist\u00eancia no WhatsApp<\/a> explica os campos adicionais.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Estou migrando de Baileys. O formato \u00e9 muito diferente?<\/h3>\n\n\n\n<p>Sim. Os eventos seguem a estrutura <code>entry[].changes[].value<\/code> da Meta, os contatos usam <code>wa_id<\/code> e a m\u00eddia vem por <code>id<\/code>. O guia <a href=\"https:\/\/vectalk.com.br\/blog\/migrar-para-api-oficial-do-whatsapp\/\">Como migrar para a API oficial do WhatsApp<\/a> cobre essa transi\u00e7\u00e3o. Para come\u00e7ar na Vectalk, crie sua conta em <a href=\"https:\/\/apps.vectalk.com.br\/register\">apps.vectalk.com.br\/register<\/a>; d\u00favidas de conta ficam na <a href=\"https:\/\/vectalk.com.br\/ajuda.html\">Central de Ajuda<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Fontes consultadas em 20\/10\/2026:<\/strong> <a href=\"https:\/\/developers.facebook.com\/docs\/whatsapp\/cloud-api\/guides\/set-up-webhooks\">Webhooks da Cloud API<\/a>, <a href=\"https:\/\/developers.facebook.com\/docs\/graph-api\/webhooks\/getting-started\">Webhooks da Graph API: primeiros passos<\/a>, <a href=\"https:\/\/developers.facebook.com\/docs\/whatsapp\/cloud-api\/webhooks\/reference\/messages\/status\">Refer\u00eancia do webhook de status<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">Webhooks na documenta\u00e7\u00e3o da Vectalk<\/a>.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[8],"tags":[],"class_list":["post-43","post","type-post","status-publish","format-standard","hentry","category-uncategorized-pt"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos - Blog Vectalk<\/title>\n<meta name=\"description\" content=\"Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos - Blog Vectalk\" \/>\n<meta property=\"og:description\" content=\"Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/\" \/>\n<meta property=\"og:site_name\" content=\"Blog Vectalk\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-20T19:07:40+00:00\" \/>\n<meta name=\"author\" content=\"wanderleyfcunha\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"wanderleyfcunha\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/\"},\"author\":{\"name\":\"wanderleyfcunha\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"headline\":\"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos\",\"datePublished\":\"2026-10-20T19:07:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/\"},\"wordCount\":1553,\"commentCount\":0,\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/\",\"url\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/\",\"name\":\"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos - Blog Vectalk\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#website\"},\"datePublished\":\"2026-10-20T19:07:40+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"description\":\"Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/webhook-whatsapp-api\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/\",\"name\":\"Blog Vectalk\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\",\"name\":\"wanderleyfcunha\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g\",\"caption\":\"wanderleyfcunha\"},\"sameAs\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos - Blog Vectalk","description":"Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/","og_locale":"en_US","og_type":"article","og_title":"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos - Blog Vectalk","og_description":"Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.","og_url":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/","og_site_name":"Blog Vectalk","article_published_time":"2026-10-20T19:07:40+00:00","author":"wanderleyfcunha","twitter_card":"summary_large_image","twitter_misc":{"Written by":"wanderleyfcunha","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/#article","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/"},"author":{"name":"wanderleyfcunha","@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"headline":"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos","datePublished":"2026-10-20T19:07:40+00:00","mainEntityOfPage":{"@id":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/"},"wordCount":1553,"commentCount":0,"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/","url":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/","name":"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos - Blog Vectalk","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/#website"},"datePublished":"2026-10-20T19:07:40+00:00","author":{"@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"description":"Webhook da API do WhatsApp na pr\u00e1tica: verifica\u00e7\u00e3o do endpoint, payloads de mensagem e status, assinatura, fila e exemplos em Node.js e Python.","breadcrumb":{"@id":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/vectalk.com.br\/blog\/webhook-whatsapp-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/vectalk.com.br\/blog\/"},{"@type":"ListItem","position":2,"name":"Webhooks da API do WhatsApp: guia pr\u00e1tico com exemplos"}]},{"@type":"WebSite","@id":"https:\/\/vectalk.com.br\/blog\/#website","url":"https:\/\/vectalk.com.br\/blog\/","name":"Blog Vectalk","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/vectalk.com.br\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b","name":"wanderleyfcunha","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g","caption":"wanderleyfcunha"},"sameAs":["https:\/\/vectalk.com.br\/blog"]}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/43","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/comments?post=43"}],"version-history":[{"count":0,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/43\/revisions"}],"wp:attachment":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/media?parent=43"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/categories?post=43"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/tags?post=43"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}