{"id":534,"date":"2026-10-28T16:06:51","date_gmt":"2026-10-28T16:06:51","guid":{"rendered":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/"},"modified":"2026-10-28T16:06:53","modified_gmt":"2026-10-28T16:06:53","slug":"bot-whatsapp-python-api-oficial","status":"publish","type":"post","link":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/","title":{"rendered":"Bot de WhatsApp com Python na API oficial (FastAPI, do zero)"},"content":{"rendered":"\n<p><em>Atualizado em 28\/10\/2026<\/em><\/p>\n\n\n\n<p>Para criar um bot de WhatsApp com Python na API oficial, voc\u00ea precisa de um servidor HTTPS com duas rotas: um <code>GET<\/code> que responde ao desafio de verifica\u00e7\u00e3o da Meta e um <code>POST<\/code> que recebe as mensagens, valida a assinatura <code>X-Hub-Signature-256<\/code> e responde chamando o endpoint <code>\/messages<\/code> da Cloud API. Com FastAPI e httpx isso cabe em um arquivo de cerca de 150 linhas, e \u00e9 o que este tutorial monta do zero.<\/p>\n\n\n\n<p>O bot final responde &#8220;oi&#8221; ou &#8220;menu&#8221; com tr\u00eas bot\u00f5es, trata o toque em cada bot\u00e3o, marca a mensagem como lida, ignora eventos repetidos e roda em produ\u00e7\u00e3o com Docker e HTTPS autom\u00e1tico. O c\u00f3digo foi testado com FastAPI 0.128 e funciona tanto direto na Meta quanto pela Vectalk, mudando s\u00f3 duas vari\u00e1veis de ambiente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Como o bot funciona<\/h2>\n\n\n\n<p>O fluxo tem quatro passos:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>O cliente manda uma mensagem para o seu n\u00famero.<\/li>\n\n\n<li>A Meta envia um <code>POST<\/code> com o evento para a URL do seu webhook.<\/li>\n\n\n<li>O seu servidor valida a assinatura, responde <code>200<\/code> na hora e processa o evento em segundo plano.<\/li>\n\n\n<li>O bot chama <code>POST \/&lt;PHONE_NUMBER_ID&gt;\/messages<\/code> para responder.<\/li>\n\n<\/ol>\n\n\n\n<p>Como a resposta sai logo depois da mensagem do cliente, ela est\u00e1 dentro da <a href=\"https:\/\/vectalk.com.br\/blog\/janela-24-horas-whatsapp\/\">janela de 24 horas<\/a> e pode ser texto livre ou mensagem interativa, sem template. Se voc\u00ea ainda est\u00e1 decidindo como acessar a API, o guia da <a href=\"https:\/\/vectalk.com.br\/blog\/api-oficial-do-whatsapp\/\">API oficial do WhatsApp<\/a> explica as op\u00e7\u00f5es.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">O que voc\u00ea precisa antes<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Vari\u00e1vel<\/th><th>O que \u00e9<\/th><th>Onde encontrar<\/th><\/tr><\/thead><tbody><tr><td><code>PHONE_NUMBER_ID<\/code><\/td><td>ID do n\u00famero na Cloud API<\/td><td>Painel de Apps, na se\u00e7\u00e3o do WhatsApp<\/td><\/tr><tr><td><code>ACCESS_TOKEN<\/code><\/td><td>Token com permiss\u00e3o <code>whatsapp_business_messaging<\/code><\/td><td>Token de usu\u00e1rio do sistema<\/td><\/tr><tr><td><code>APP_SECRET<\/code><\/td><td>Chave secreta do app, usada na assinatura<\/td><td>Configura\u00e7\u00f5es do app &gt; B\u00e1sico<\/td><\/tr><tr><td><code>VERIFY_TOKEN<\/code><\/td><td>Texto qualquer escolhido por voc\u00ea<\/td><td>Voc\u00ea define e repete no painel<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Use um <a href=\"https:\/\/vectalk.com.br\/blog\/token-permanente-api-whatsapp\/\">token permanente de usu\u00e1rio do sistema<\/a>: o token tempor\u00e1rio do painel expira e derruba o bot. O n\u00famero tamb\u00e9m precisa estar registrado na Cloud API; se ainda n\u00e3o est\u00e1, veja <a href=\"https:\/\/vectalk.com.br\/blog\/registrar-numero-cloud-api\/\">como registrar o n\u00famero<\/a>.<\/p>\n\n\n\n<p>No ambiente local, voc\u00ea precisa de Python 3.10 ou mais recente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Prepare o projeto<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">mkdir bot-whatsapp &amp;&amp; cd bot-whatsapp\npython -m venv .venv\nsource .venv\/bin\/activate\npip install \"fastapi[standard]\" httpx<\/code><\/pre>\n\n\n\n<p>O pacote <code>fastapi[standard]<\/code> traz o Uvicorn e o comando <code>fastapi<\/code>. O httpx faz as chamadas ass\u00edncronas para a API.<\/p>\n\n\n\n<p>Crie o arquivo <code>main.py<\/code>. Os blocos de c\u00f3digo das se\u00e7\u00f5es 1 a 5 formam o arquivo completo: cole cada um em sequ\u00eancia.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">import hashlib\nimport hmac\nimport json\nimport logging\nimport os\n\nimport httpx\nfrom fastapi import BackgroundTasks, FastAPI, HTTPException, Request\nfrom fastapi.responses import PlainTextResponse\n\nVERIFY_TOKEN = os.environ[\"VERIFY_TOKEN\"]\nACCESS_TOKEN = os.environ[\"ACCESS_TOKEN\"]\nAPP_SECRET = os.environ.get(\"APP_SECRET\", \"\")\nWEBHOOK_KEY = os.environ.get(\"WEBHOOK_KEY\", \"\")\nPHONE_NUMBER_ID = os.environ.get(\"PHONE_NUMBER_ID\", \"\")\nAPI_URL = os.environ.get(\n    \"API_URL\", f\"https:\/\/graph.facebook.com\/v26.0\/{PHONE_NUMBER_ID}\/messages\"\n)\n\nlog = logging.getLogger(\"bot\")\napp = FastAPI()\nclient = httpx.AsyncClient(timeout=10)\nprocessed = set()  # em produ\u00e7\u00e3o, use Redis ou o banco<\/code><\/pre>\n\n\n\n<p><code>API_URL<\/code> tem a URL da Meta como padr\u00e3o. Para usar a Vectalk, voc\u00ea s\u00f3 troca essa vari\u00e1vel e o token, como mostra a se\u00e7\u00e3o sobre a Vectalk mais abaixo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Verifica\u00e7\u00e3o do webhook (GET)<\/h2>\n\n\n\n<p>Quando voc\u00ea salva a URL de callback no painel, a Meta manda um <code>GET<\/code> com <code>hub.mode=subscribe<\/code>, <code>hub.verify_token<\/code> e <code>hub.challenge<\/code>. Se o token bater com o seu, responda <code>200<\/code> com o valor de <code>hub.challenge<\/code> em texto puro. Qualquer outra resposta deixa o endpoint como n\u00e3o verificado, e a Meta n\u00e3o envia eventos.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">@app.get(\"\/webhook\")\ndef verify(request: Request):\n    q = request.query_params\n    if q.get(\"hub.mode\") == \"subscribe\" and q.get(\"hub.verify_token\") == VERIFY_TOKEN:\n        return PlainTextResponse(q.get(\"hub.challenge\", \"\"))\n    raise HTTPException(status_code=403)<\/code><\/pre>\n\n\n\n<p>Os nomes dos par\u00e2metros t\u00eam ponto (<code>hub.mode<\/code>), por isso o c\u00f3digo l\u00ea <code>request.query_params<\/code> em vez de declarar par\u00e2metros na fun\u00e7\u00e3o.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Validar a assinatura X-Hub-Signature-256<\/h2>\n\n\n\n<p>Todo <code>POST<\/code> da Meta traz o cabe\u00e7alho <code>X-Hub-Signature-256: sha256=&lt;hash&gt;<\/code>. O hash \u00e9 um HMAC-SHA256 do corpo da requisi\u00e7\u00e3o, calculado com a chave secreta do app. Sem essa checagem, qualquer pessoa que descubra sua URL consegue fingir mensagens de clientes.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">def signature_ok(body: bytes, header: str) -&gt; bool:\n    if not APP_SECRET:\n        return True\n    expected = \"sha256=\" + hmac.new(APP_SECRET.encode(), body, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, header or \"\")<\/code><\/pre>\n\n\n\n<p>Dois cuidados que evitam horas de depura\u00e7\u00e3o:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Use os bytes originais.<\/strong> Calcule o HMAC sobre <code>await request.body()<\/code>, nunca sobre um JSON reserializado. Uma v\u00edrgula ou um espa\u00e7o diferente muda o hash.<\/li>\n\n\n<li><strong>Compare em tempo constante.<\/strong> <code>hmac.compare_digest<\/code> evita que o tempo da compara\u00e7\u00e3o revele o hash.<\/li>\n\n<\/ul>\n\n\n\n<p>O artigo sobre <a href=\"https:\/\/vectalk.com.br\/blog\/assinatura-webhook-whatsapp\/\">validar a assinatura do webhook<\/a> aprofunda o tema e mostra os erros mais comuns.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Receber o evento e responder r\u00e1pido<\/h2>\n\n\n\n<p>A Meta espera <code>200<\/code> r\u00e1pido. Se a entrega falha, ela tenta de novo com frequ\u00eancia cada vez menor por at\u00e9 7 dias, ent\u00e3o o mesmo evento pode chegar mais de uma vez. Por isso o c\u00f3digo responde antes de processar e ignora IDs de mensagem j\u00e1 vistos.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">@app.post(\"\/webhook\")\nasync def receive(request: Request, background: BackgroundTasks):\n    if WEBHOOK_KEY and request.query_params.get(\"key\") != WEBHOOK_KEY:\n        raise HTTPException(status_code=403)\n    body = await request.body()\n    if not signature_ok(body, request.headers.get(\"x-hub-signature-256\", \"\")):\n        raise HTTPException(status_code=401, detail=\"assinatura invalida\")\n    background.add_task(process, json.loads(body))\n    return {\"ok\": True}\n\n\nasync def process(payload: dict) -&gt; None:\n    for entry in payload.get(\"entry\", []):\n        for change in entry.get(\"changes\", []):\n            for msg in change.get(\"value\", {}).get(\"messages\", []):\n                if msg[\"id\"] in processed:\n                    continue  # a Meta pode reenviar o mesmo evento\n                processed.add(msg[\"id\"])\n                try:\n                    await handle(msg)\n                except httpx.HTTPError:\n                    log.exception(\"falha ao responder %s\", msg[\"id\"])<\/code><\/pre>\n\n\n\n<p>O <code>BackgroundTasks<\/code> do FastAPI roda <code>process<\/code> depois que a resposta \u00e9 enviada. Eventos de status (<code>sent<\/code>, <code>delivered<\/code>, <code>read<\/code>, <code>failed<\/code>) chegam no mesmo webhook, dentro de <code>value.statuses<\/code>, e este bot os ignora. Para trat\u00e1-los, veja <a href=\"https:\/\/vectalk.com.br\/blog\/status-mensagem-webhook-whatsapp\/\">status de mensagem via webhook<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Responder texto e bot\u00f5es interativos<\/h2>\n\n\n\n<p>A l\u00f3gica do bot fica em <code>handle<\/code>. Texto &#8220;oi&#8221; ou &#8220;menu&#8221; abre o menu de bot\u00f5es; o toque em um bot\u00e3o chega como <code>type: \"interactive\"<\/code> com <code>interactive.type: \"button_reply\"<\/code> e o <code>id<\/code> que voc\u00ea definiu no envio.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">ANSWERS = {\n    \"horario\": \"Atendemos de segunda a sexta, das 9h \u00e0s 18h.\",\n    \"precos\": \"Veja a tabela completa em https:\/\/example.com\/precos\",\n    \"humano\": \"Certo! Um atendente vai continuar a conversa por aqui.\",\n}\n\n\nasync def handle(msg: dict) -&gt; None:\n    dest = recipient_of(msg)\n    await mark_read(msg[\"id\"])\n    kind = msg.get(\"type\")\n    if kind == \"text\":\n        text = msg[\"text\"][\"body\"].strip().lower()\n        if text in {\"oi\", \"ol\u00e1\", \"ola\", \"menu\"}:\n            await send_menu(dest)\n        else:\n            await send_text(dest, \"N\u00e3o entendi. Digite *menu* para ver as op\u00e7\u00f5es.\")\n    elif kind == \"interactive\" and msg[\"interactive\"][\"type\"] == \"button_reply\":\n        choice = msg[\"interactive\"][\"button_reply\"][\"id\"]\n        await send_text(dest, ANSWERS.get(choice, \"Op\u00e7\u00e3o inv\u00e1lida.\"))\n    else:\n        await send_text(dest, \"Por enquanto eu entendo s\u00f3 texto e bot\u00f5es.\")\n\n\ndef recipient_of(msg: dict) -&gt; dict:\n    # Usu\u00e1rios com nome de usu\u00e1rio podem chegar sem \"from\", s\u00f3 com o BSUID\n    if \"from\" in msg:\n        return {\"to\": msg[\"from\"]}\n    return {\"recipient\": msg[\"from_user_id\"]}<\/code><\/pre>\n\n\n\n<p>A fun\u00e7\u00e3o <code>recipient_of<\/code> j\u00e1 prepara o bot para os nomes de usu\u00e1rio do WhatsApp. Segundo a documenta\u00e7\u00e3o da Meta, quando o cliente adota um nome de usu\u00e1rio, o campo <code>from<\/code> pode n\u00e3o vir no webhook, mas o identificador <code>from_user_id<\/code> (BSUID) vem sempre, e desde julho de 2026 \u00e9 poss\u00edvel enviar mensagens para ele no campo <code>recipient<\/code>. O artigo sobre <a href=\"https:\/\/vectalk.com.br\/blog\/usernames-whatsapp-bsuid\/\">usernames e BSUID<\/a> explica a mudan\u00e7a.<\/p>\n\n\n\n<p>Por fim, as fun\u00e7\u00f5es que chamam a API:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">async def call_api(payload: dict) -&gt; dict:\n    r = await client.post(\n        API_URL,\n        headers={\"Authorization\": f\"Bearer {ACCESS_TOKEN}\"},\n        json={\"messaging_product\": \"whatsapp\", **payload},\n    )\n    if r.is_error:\n        log.error(\"API %s: %s\", r.status_code, r.text)\n    r.raise_for_status()\n    return r.json()\n\n\nasync def mark_read(message_id: str) -&gt; None:\n    try:\n        await call_api({\n            \"status\": \"read\",\n            \"message_id\": message_id,\n            \"typing_indicator\": {\"type\": \"text\"},\n        })\n    except httpx.HTTPError:\n        pass  # n\u00e3o impede a resposta\n\n\nasync def send_text(dest: dict, text: str) -&gt; dict:\n    return await call_api({\n        \"recipient_type\": \"individual\",\n        **dest,\n        \"type\": \"text\",\n        \"text\": {\"body\": text},\n    })\n\n\nasync def send_menu(dest: dict) -&gt; dict:\n    buttons = [(\"horario\", \"Hor\u00e1rio\"), (\"precos\", \"Pre\u00e7os\"), (\"humano\", \"Falar com atendente\")]\n    return await call_api({\n        \"recipient_type\": \"individual\",\n        **dest,\n        \"type\": \"interactive\",\n        \"interactive\": {\n            \"type\": \"button\",\n            \"body\": {\"text\": \"Ol\u00e1! Como posso ajudar?\"},\n            \"action\": {\n                \"buttons\": [\n                    {\"type\": \"reply\", \"reply\": {\"id\": i, \"title\": t}} for i, t in buttons\n                ]\n            },\n        },\n    })<\/code><\/pre>\n\n\n\n<p><code>mark_read<\/code> marca a mensagem como lida e mostra &#8220;digitando&#8230;&#8221; para o cliente. A Meta remove o indicador quando voc\u00ea responde ou depois de 25 segundos.<\/p>\n\n\n\n<p>Os limites dos bot\u00f5es de resposta, segundo a documenta\u00e7\u00e3o da Meta:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Campo<\/th><th>Limite<\/th><\/tr><\/thead><tbody><tr><td>Bot\u00f5es por mensagem<\/td><td>at\u00e9 3<\/td><\/tr><tr><td><code>title<\/code> do bot\u00e3o<\/td><td>20 caracteres<\/td><\/tr><tr><td><code>id<\/code> do bot\u00e3o<\/td><td>256 caracteres<\/td><\/tr><tr><td><code>body.text<\/code><\/td><td>1.024 caracteres<\/td><\/tr><tr><td><code>footer.text<\/code><\/td><td>60 caracteres<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Para listas com at\u00e9 10 op\u00e7\u00f5es e outros formatos, veja <a href=\"https:\/\/vectalk.com.br\/blog\/mensagens-interativas-whatsapp-api\/\">mensagens interativas<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Testar localmente<\/h2>\n\n\n\n<p>Exporte as vari\u00e1veis e suba o servidor em modo de desenvolvimento, com recarga autom\u00e1tica:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">export VERIFY_TOKEN=\"um-texto-qualquer\"\nexport ACCESS_TOKEN=\"EAAG...\"\nexport APP_SECRET=\"sua-chave-secreta\"\nexport PHONE_NUMBER_ID=\"123456789012345\"\nfastapi dev main.py<\/code><\/pre>\n\n\n\n<p>Teste a verifica\u00e7\u00e3o e um evento assinado sem depender da Meta:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">curl \"http:\/\/127.0.0.1:8000\/webhook?hub.mode=subscribe&amp;hub.verify_token=um-texto-qualquer&amp;hub.challenge=42\"\n\nBODY='{\"entry\":[{\"changes\":[{\"value\":{\"messages\":[{\"from\":\"5511999999999\",\"id\":\"wamid.teste1\",\"type\":\"text\",\"text\":{\"body\":\"menu\"}}]}}]}]}'\nSIG=$(printf '%s' \"$BODY\" | openssl dgst -sha256 -hmac \"$APP_SECRET\" | awk '{print $NF}')\ncurl -X POST http:\/\/127.0.0.1:8000\/webhook \\\n  -H \"Content-Type: application\/json\" \\\n  -H \"X-Hub-Signature-256: sha256=$SIG\" \\\n  --data-binary \"$BODY\"<\/code><\/pre>\n\n\n\n<p>O primeiro comando deve devolver <code>42<\/code>. O segundo devolve <code>{\"ok\":true}<\/code> e faz o bot enviar o menu para o n\u00famero do campo <code>from<\/code> (use o seu n\u00famero de teste).<\/p>\n\n\n\n<p>Para receber eventos reais, a Meta exige uma URL p\u00fablica com certificado TLS v\u00e1lido. Em desenvolvimento, um t\u00fanel resolve: o guia <a href=\"https:\/\/vectalk.com.br\/blog\/testar-webhook-whatsapp-localmente\/\">testar o webhook localmente com ngrok<\/a> mostra o passo a passo. Com a URL p\u00fablica em m\u00e3os, v\u00e1 em Painel de Apps &gt; WhatsApp &gt; Configura\u00e7\u00e3o, preencha a URL de callback (<code>https:\/\/...\/webhook<\/code>) e o token de verifica\u00e7\u00e3o, salve e assine o campo <code>messages<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Deploy simples com Docker e HTTPS<\/h2>\n\n\n\n<p>Crie o <code>requirements.txt<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">fastapi[standard]\nhttpx<\/code><\/pre>\n\n\n\n<p>E o <code>Dockerfile<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">FROM python:3.12-slim\nWORKDIR \/app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY main.py .\nCMD [\"fastapi\", \"run\", \"main.py\", \"--port\", \"8000\"]<\/code><\/pre>\n\n\n\n<p><code>fastapi run<\/code> \u00e9 o modo de produ\u00e7\u00e3o, sem recarga autom\u00e1tica. Em um servidor com Docker:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">docker build -t bot-whatsapp .\ndocker run -d --restart unless-stopped -p 127.0.0.1:8000:8000 \\\n  -e VERIFY_TOKEN -e ACCESS_TOKEN -e APP_SECRET -e PHONE_NUMBER_ID \\\n  bot-whatsapp<\/code><\/pre>\n\n\n\n<p>Falta o HTTPS. Uma op\u00e7\u00e3o simples \u00e9 o Caddy, que obt\u00e9m e renova o certificado sozinho quando o DNS do dom\u00ednio aponta para o servidor e as portas 80 e 443 est\u00e3o abertas. O <code>Caddyfile<\/code> inteiro tem duas linhas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">bot.suaempresa.com.br\nreverse_proxy 127.0.0.1:8000<\/code><\/pre>\n\n\n\n<p>Atualize a URL de callback no painel para <code>https:\/\/bot.suaempresa.com.br\/webhook<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Checklist antes de colocar em produ\u00e7\u00e3o<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Deduplica\u00e7\u00e3o persistente.<\/strong> O <code>set<\/code> em mem\u00f3ria some a cada rein\u00edcio e n\u00e3o \u00e9 compartilhado entre processos. Guarde os IDs processados no Redis ou no banco, com expira\u00e7\u00e3o.<\/li>\n\n\n<li><strong>Fila.<\/strong> Se a resposta depende de IA, banco ou sistemas lentos, troque o <code>BackgroundTasks<\/code> por uma fila (Redis, RabbitMQ, SQS) com worker separado.<\/li>\n\n\n<li><strong>Um processo por enquanto.<\/strong> Com o <code>set<\/code> em mem\u00f3ria, rode um \u00fanico worker. Ao escalar, mova o estado para fora do processo.<\/li>\n\n\n<li><strong>Logs dos erros da API.<\/strong> O c\u00f3digo registra o corpo de erro da Meta. A tabela de <a href=\"https:\/\/vectalk.com.br\/blog\/erros-api-whatsapp\/\">erros da API do WhatsApp<\/a> ajuda a interpretar os c\u00f3digos.<\/li>\n\n\n<li><strong>Segredos fora do c\u00f3digo.<\/strong> Token e chave secreta s\u00f3 em vari\u00e1veis de ambiente ou num cofre de segredos.<\/li>\n\n\n<li><strong>M\u00eddia.<\/strong> Imagens, \u00e1udios e documentos chegam no webhook s\u00f3 com um ID; o download \u00e9 uma chamada separada \u00e0 API de m\u00eddia.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Como fazer com a Vectalk<\/h2>\n\n\n\n<p>A Vectalk \u00e9 um proxy da WhatsApp Cloud API oficial: os payloads de envio e os eventos de webhook s\u00e3o os mesmos da Meta, ent\u00e3o o <code>main.py<\/code> funciona sem mudan\u00e7as de l\u00f3gica. Mudam tr\u00eas coisas:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>URL e token.<\/strong> Defina <code>API_URL=https:\/\/apis.vectalk.com.br\/api\/messages<\/code> e use em <code>ACCESS_TOKEN<\/code> um token de inst\u00e2ncia gerado na Vectalk. Com ele, o <code>phone_number_id<\/code> \u00e9 resolvido automaticamente.<\/li>\n\n\n<li><strong>Cadastro do webhook por API.<\/strong> Voc\u00ea registra a URL com <code>POST https:\/\/apis.vectalk.com.br\/api\/webhooks<\/code>, informando <code>instance_id<\/code>, <code>url<\/code> e <code>name<\/code>, e pode ter v\u00e1rias URLs por inst\u00e2ncia.<\/li>\n\n\n<li><strong>Prote\u00e7\u00e3o do endpoint.<\/strong> A <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">documenta\u00e7\u00e3o de webhooks da Vectalk<\/a> n\u00e3o descreve um cabe\u00e7alho de assinatura. Deixe <code>APP_SECRET<\/code> vazio, defina <code>WEBHOOK_KEY<\/code> com um valor aleat\u00f3rio longo e cadastre a URL como <code>https:\/\/bot.suaempresa.com.br\/webhook?key=SEU_VALOR<\/code>.<\/li>\n\n<\/ol>\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_VECTALK\" \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"instance_id\": \"SUA_INSTANCIA\", \"url\": \"https:\/\/bot.suaempresa.com.br\/webhook?key=SEU_VALOR\", \"name\": \"Bot Python\"}'<\/code><\/pre>\n\n\n\n<p>A Vectalk espera <code>200<\/code> em menos de 20 segundos e reenvia o evento se n\u00e3o receber, o que o c\u00f3digo j\u00e1 trata com a deduplica\u00e7\u00e3o. Crie sua conta em <a href=\"https:\/\/apps.vectalk.com.br\/register\">apps.vectalk.com.br\/register<\/a> e consulte a <a href=\"https:\/\/docs.vectalk.com.br\">documenta\u00e7\u00e3o<\/a> para os demais endpoints. Se o pr\u00f3ximo passo \u00e9 responder com IA, o tutorial <a href=\"https:\/\/vectalk.com.br\/blog\/chatgpt-api-oficial-whatsapp\/\">ChatGPT na API oficial do WhatsApp<\/a> parte de uma base parecida com esta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Perguntas frequentes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Preciso de uma biblioteca espec\u00edfica de WhatsApp para Python?<\/h3>\n\n\n\n<p>N\u00e3o. A Cloud API \u00e9 HTTP com JSON, ent\u00e3o FastAPI para receber e httpx (ou requests) para enviar resolvem. Bibliotecas de terceiros podem poupar c\u00f3digo, mas acompanhar a API direto deixa voc\u00ea livre para usar recursos novos assim que a Meta os lan\u00e7a.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">O bot pode iniciar a conversa?<\/h3>\n\n\n\n<p>Texto livre e bot\u00f5es s\u00f3 funcionam dentro da janela de 24 horas aberta pela \u00faltima mensagem do cliente. Para iniciar uma conversa ou responder depois desse prazo, \u00e9 preciso enviar um template aprovado.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Quanto custa manter um bot respondendo?<\/h3>\n\n\n\n<p>Respostas dentro da janela de atendimento s\u00e3o mensagens de servi\u00e7o. Desde 1\u00ba de outubro de 2026, cada n\u00famero tem uma franquia de 1.000 mensagens de servi\u00e7o por m\u00eas; a partir da 1.001\u00aa, a tarifa para destinat\u00e1rios no Brasil \u00e9 de R$ 0,0350 por mensagem.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Por que a verifica\u00e7\u00e3o do webhook falha no painel?<\/h3>\n\n\n\n<p>As causas mais comuns s\u00e3o: a URL n\u00e3o \u00e9 HTTPS com certificado v\u00e1lido, o <code>VERIFY_TOKEN<\/code> do servidor \u00e9 diferente do digitado no painel, ou a rota devolve o desafio como JSON em vez de texto puro. O c\u00f3digo acima usa <code>PlainTextResponse<\/code> justamente por isso.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Posso usar Flask ou Django em vez de FastAPI?<\/h3>\n\n\n\n<p>Pode. O que importa \u00e9 a mesma sequ\u00eancia: responder ao <code>GET<\/code> de verifica\u00e7\u00e3o, validar o HMAC sobre o corpo bruto, devolver <code>200<\/code> r\u00e1pido e chamar o endpoint de mensagens. Em Flask, o corpo bruto vem de <code>request.get_data()<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Fontes oficiais consultadas em 28\/10\/2026:<\/strong> <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/create-webhook-endpoint\" target=\"_blank\" rel=\"noreferrer noopener\">Create a webhook endpoint (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">Webhooks overview (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/messages\/interactive-reply-buttons-messages\" target=\"_blank\" rel=\"noreferrer noopener\">Interactive reply buttons messages (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/reference\/messages\/interactive\" target=\"_blank\" rel=\"noreferrer noopener\">Interactive messages webhook (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/typing-indicators\" target=\"_blank\" rel=\"noreferrer noopener\">Typing indicators (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/business-scoped-user-ids\" target=\"_blank\" rel=\"noreferrer noopener\">Business-scoped user IDs (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/pricing\" target=\"_blank\" rel=\"noreferrer noopener\">Pre\u00e7os (Meta)<\/a>, <a href=\"https:\/\/fastapi.tiangolo.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastAPI<\/a>, <a href=\"https:\/\/caddyserver.com\/docs\/quick-starts\/reverse-proxy\" target=\"_blank\" rel=\"noreferrer noopener\">Caddy: reverse proxy quick-start<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">Vectalk: webhooks<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/authentication\">Vectalk: autentica\u00e7\u00e3o<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/api-reference\/messages\/send-buttons\">Vectalk: bot\u00f5es de resposta<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tutorial completo para criar um bot de WhatsApp em Python com FastAPI na API oficial: webhook, assinatura, texto, bot\u00f5es e deploy com HTTPS.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","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-534","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>Bot de WhatsApp com Python e FastAPI na API | Vectalk<\/title>\n<meta name=\"description\" content=\"Tutorial WhatsApp API Python com FastAPI: verifica\u00e7\u00e3o do webhook, assinatura X-Hub-Signature-256, respostas, bot\u00f5es interativos e deploy com HTTPS.\" \/>\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\/bot-whatsapp-python-api-oficial\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Bot de WhatsApp com Python e FastAPI na API | Vectalk\" \/>\n<meta property=\"og:description\" content=\"Tutorial WhatsApp API Python com FastAPI: verifica\u00e7\u00e3o do webhook, assinatura X-Hub-Signature-256, respostas, bot\u00f5es interativos e deploy com HTTPS.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/\" \/>\n<meta property=\"og:site_name\" content=\"Blog Vectalk\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-28T16:06:51+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-28T16:06:53+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=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/\"},\"author\":{\"name\":\"wanderleyfcunha\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"headline\":\"Bot de WhatsApp com Python na API oficial (FastAPI, do zero)\",\"datePublished\":\"2026-10-28T16:06:51+00:00\",\"dateModified\":\"2026-10-28T16:06:53+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/\"},\"wordCount\":1700,\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/\",\"url\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/\",\"name\":\"Bot de WhatsApp com Python e FastAPI na API | Vectalk\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#website\"},\"datePublished\":\"2026-10-28T16:06:51+00:00\",\"dateModified\":\"2026-10-28T16:06:53+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"description\":\"Tutorial WhatsApp API Python com FastAPI: verifica\u00e7\u00e3o do webhook, assinatura X-Hub-Signature-256, respostas, bot\u00f5es interativos e deploy com HTTPS.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/bot-whatsapp-python-api-oficial\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Bot de WhatsApp com Python na API oficial (FastAPI, do zero)\"}]},{\"@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":"Bot de WhatsApp com Python e FastAPI na API | Vectalk","description":"Tutorial WhatsApp API Python com FastAPI: verifica\u00e7\u00e3o do webhook, assinatura X-Hub-Signature-256, respostas, bot\u00f5es interativos e deploy com HTTPS.","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\/bot-whatsapp-python-api-oficial\/","og_locale":"en_US","og_type":"article","og_title":"Bot de WhatsApp com Python e FastAPI na API | Vectalk","og_description":"Tutorial WhatsApp API Python com FastAPI: verifica\u00e7\u00e3o do webhook, assinatura X-Hub-Signature-256, respostas, bot\u00f5es interativos e deploy com HTTPS.","og_url":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/","og_site_name":"Blog Vectalk","article_published_time":"2026-10-28T16:06:51+00:00","article_modified_time":"2026-10-28T16:06:53+00:00","author":"wanderleyfcunha","twitter_card":"summary_large_image","twitter_misc":{"Written by":"wanderleyfcunha","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/#article","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/"},"author":{"name":"wanderleyfcunha","@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"headline":"Bot de WhatsApp com Python na API oficial (FastAPI, do zero)","datePublished":"2026-10-28T16:06:51+00:00","dateModified":"2026-10-28T16:06:53+00:00","mainEntityOfPage":{"@id":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/"},"wordCount":1700,"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/","url":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/","name":"Bot de WhatsApp com Python e FastAPI na API | Vectalk","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/#website"},"datePublished":"2026-10-28T16:06:51+00:00","dateModified":"2026-10-28T16:06:53+00:00","author":{"@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"description":"Tutorial WhatsApp API Python com FastAPI: verifica\u00e7\u00e3o do webhook, assinatura X-Hub-Signature-256, respostas, bot\u00f5es interativos e deploy com HTTPS.","breadcrumb":{"@id":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/vectalk.com.br\/blog\/bot-whatsapp-python-api-oficial\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/vectalk.com.br\/blog\/"},{"@type":"ListItem","position":2,"name":"Bot de WhatsApp com Python na API oficial (FastAPI, do zero)"}]},{"@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\/534","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=534"}],"version-history":[{"count":1,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/534\/revisions"}],"predecessor-version":[{"id":537,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/534\/revisions\/537"}],"wp:attachment":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/media?parent=534"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/categories?post=534"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/tags?post=534"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}