---
title: "Como criar um bot de WhatsApp com a API não oficial (do zero ao menu com botões)"
description: "Crie um bot de WhatsApp com a API não oficial: webhook em Node.js, máquina de estados, menu com lista e botões, 'digitando...' e passagem para humano."
url: "https://wame.api.br/blog/criar-bot-whatsapp-api-nao-oficial"
language: "pt-BR"
og:type: "article"
published_time: "2026-09-28"
modified_time: "2026-09-28"
author: "Raphael Serafim"
keywords: "api whatsapp não oficial, api não oficial whatsapp, criar bot whatsapp, bot whatsapp node.js, chatbot whatsapp api, menu com botões whatsapp api, bot de atendimento whatsapp"
reading_time: "10 min de leitura"
---

# Como criar um bot de WhatsApp com a API não oficial (do zero ao menu com botões)

> Crie um bot de WhatsApp com a API não oficial: webhook em Node.js, máquina de estados, menu com lista e botões, 'digitando...' e passagem para humano.

**Para criar um bot de WhatsApp com a API não oficial, você conecta o número a uma instância da WAME por QR Code, aponta o webhook para o seu servidor e, a cada mensagem recebida, decide a resposta com uma máquina de estados — enviando texto, listas e botões pelos endpoints `/message/text`, `/message/list` e `/message/button_reply`.** Sem cadastro de app na Meta, sem template pré-aprovado e sem janela de 24 horas: o bot responde com texto livre, na hora.

Este guia monta um bot de atendimento completo em Node.js, do zero: recebe a mensagem, mostra um menu, segue o fluxo de acordo com a escolha, simula "digitando..." e passa para um humano quando precisa. É um bot de **regras** — previsível, barato e suficiente para a maior parte do atendimento repetitivo. Se você quer respostas geradas por IA, o [chatbot com a API da OpenAI](/blog/chatbot-ia-openai-whatsapp) parte da mesma base.

## Por que a API não oficial é boa para bots

- **Começa em minutos.** Crie a instância, leia o QR Code em Aparelhos conectados e o número já está pronto. Prefere não usar QR? Há o [código de pareamento](/blog/conectar-whatsapp-codigo-pareamento-sem-qr-code).
- **Texto livre sempre.** Nada de template aprovado para iniciar ou retomar conversa — detalhes em [API do WhatsApp sem template](/blog/api-whatsapp-sem-template-texto-livre).
- **Recursos ricos**: listas, botões, enquetes, reações, figurinhas, grupos. Veja todos em [recursos da API não oficial](/blog/api-nao-oficial-whatsapp-recursos-exemplos).
- **Hospedada e gerenciada.** Diferente de rodar Baileys no seu servidor, a WAME cuida da sessão, da reconexão e da infraestrutura — 99,9% de uptime, suporte 24/7 em português, plataforma no ar desde 2017.

Uma observação honesta: a camada não oficial não é afiliada nem endossada pelo WhatsApp ou pela Meta, e o uso é de responsabilidade de quem opera o número.

## Passo 1: conectar e configurar o webhook

Gere o QR Code da instância:

```bash
curl -X POST "https://server.wame.api.br/SUA_KEY/instance"
```

Depois de escanear, configure para onde vão os eventos. Use o formato `meta`, que entrega tudo no envelope da Cloud API:

```bash
curl -X PUT "https://server.wame.api.br/SUA_KEY/instance" \
  -H "Content-Type: application/json" \
  -d '{
    "allowWebhook": true,
    "allowNumber": "all",
    "webhookMessage": "https://seu-servidor.com/webhook/wame",
    "webhookFormat": "meta"
  }'
```

Em desenvolvimento, exponha a porta local com ngrok ou similar.

## Passo 2: receber e extrair a mensagem

O evento chega assim:

```json
{
  "object": "wame",
  "provider": "whatsapp",
  "entry": [{
    "changes": [{
      "field": "messages",
      "value": {
        "messages": [{
          "from": "5511999999999",
          "id": "wamid.XXXX",
          "type": "text",
          "text": { "body": "oi" }
        }]
      }
    }]
  }]
}
```

Um extrator com as guardas que evitam loop e erro:

```js
function extrair(body) {
  const msg = body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
  if (!msg) return null; // status de entrega também chega aqui

  // Texto digitado ou escolha de lista/botão.
  // Os campos da resposta interativa estão descritos em /docs.
  const texto = msg.text?.body ?? msg.interactive?.list_reply?.id
    ?? msg.interactive?.button_reply?.id;
  if (!texto) return null;

  return { de: msg.from, id: msg.id, texto: String(texto).trim() };
}
```

## Passo 3: a máquina de estados

O erro clássico de bot é um amontoado de `if` que não sabe em que ponto da conversa o cliente está. A solução é guardar um **estado por contato** e ter uma função por estado:

```js
const sessoes = new Map(); // em produção: Redis ou banco

const fluxo = {
  inicio: async (c) => {
    await enviarMenu(c.de);
    return 'menu';
  },

  menu: async (c) => {
    switch (c.texto) {
      case 'pedido':
        await enviarTexto(c.de, 'Me passa o número do pedido, por favor.');
        return 'aguardando_pedido';
      case 'boleto':
        await enviarTexto(c.de, 'Qual o CPF ou CNPJ do cadastro?');
        return 'aguardando_documento';
      case 'humano':
        await enviarTexto(c.de, 'Certo! Já chamo alguém da equipe. 🙋');
        await avisarEquipe(c.de);
        return 'humano';
      default:
        await enviarMenu(c.de);
        return 'menu';
    }
  },

  aguardando_pedido: async (c) => {
    const status = await consultarPedido(c.texto); // seu sistema
    await enviarTexto(c.de, status ?? 'Não achei esse pedido. Confere o número?');
    return status ? 'inicio' : 'aguardando_pedido';
  },

  aguardando_documento: async (c) => {
    const link = await gerarSegundaVia(c.texto); // seu sistema
    await enviarTexto(c.de, link ? `Aqui está: ${link}` : 'Não encontrei esse documento.');
    return 'inicio';
  },

  humano: async () => 'humano', // bot em silêncio até a equipe liberar
};
```

Cada estado recebe a mensagem, faz o que precisa e **devolve o próximo estado**. Adicionar um fluxo novo é adicionar uma função, sem mexer nas outras.

## Passo 4: o menu com lista

Para mais de três opções, a lista é melhor que botões:

```js
const BASE = 'https://server.wame.api.br/SUA_KEY';

async function post(caminho, corpo) {
  const r = await fetch(`${BASE}${caminho}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(corpo),
  });
  if (!r.ok) throw new Error(`${caminho} ${r.status}`);
}

const enviarTexto = (to, text) => post('/message/text', { to, text });

const enviarMenu = (to) =>
  post('/message/list', {
    to,
    title: 'Atendimento',
    text: 'Como posso ajudar?',
    buttonText: 'Ver opções',
    footer: 'Responda a qualquer momento',
    sections: [{
      title: 'Opções',
      rows: [
        { title: 'Status do pedido', rowId: 'pedido' },
        { title: 'Segunda via de boleto', rowId: 'boleto' },
        { title: 'Falar com atendente', rowId: 'humano' },
      ],
    }],
  });
```

Para decisões curtas (sim/não, confirmar/cancelar), use botões de resposta rápida:

```js
const confirmar = (to) =>
  post('/message/button_reply', {
    to,
    header: { title: 'Confirmação' },
    text: 'Posso agendar para amanhã às 10h?',
    buttons: [
      { type: 'quick_reply', id: 'sim', text: 'Pode sim' },
      { type: 'quick_reply', id: 'nao', text: 'Outro horário' },
    ],
  });
```

Os `rowId` e `id` são o que volta no webhook quando o cliente escolhe — por isso o estado `menu` compara com `'pedido'`, `'boleto'` e `'humano'`.

### O detalhe da primeira mensagem

Um comportamento pouco conhecido: o WhatsApp do destinatário **não mostra botões nem listas na primeira mensagem de uma conversa que nunca existiu**. Se o bot inicia o contato mandando um menu, o cliente pode não ver nada. A solução é **abrir com um texto** antes do menu: uma mensagem normal já basta, e o menu seguinte chega certo. Em conversas iniciadas pelo cliente, como neste bot, a questão nem aparece.

## Passo 5: parecer gente (digitando e lido)

Resposta em 0 milissegundos tem cara de robô — para o cliente e para o WhatsApp. Antes de responder, marque como lida e mostre "digitando...":

```js
async function prepararResposta(to, messageId) {
  await post('/message/read', { messageId });
  await post('/message/presence', { to, status: 'composing' });
  await new Promise((r) => setTimeout(r, 1200));
}
```

A WAME já aplica um ritmo humano de leitura e digitação por trás de cada instância, proporcional ao tamanho do texto; o trecho acima é para quando você quer controlar isso explicitamente. Mais em [como simular "digitando..." pela API](/blog/tutorial-presenca-digitando-whatsapp-api).

## Passo 6: juntar tudo no webhook

```js
import express from 'express';
const app = express();
app.use(express.json());

app.post('/webhook/wame', async (req, res) => {
  res.sendStatus(200); // responda já; o processamento vem depois

  const c = extrair(req.body);
  if (!c) return;

  const estado = sessoes.get(c.de) ?? 'inicio';
  if (estado === 'humano') return; // atendente no comando

  try {
    await prepararResposta(c.de, c.id);
    const proximo = await fluxo[estado](c);
    sessoes.set(c.de, proximo);
  } catch (e) {
    console.error('falha no bot', c.de, e);
    await enviarTexto(c.de, 'Tive um problema aqui. Já chamo alguém da equipe.');
    sessoes.set(c.de, 'humano');
  }
});

app.listen(3000);
```

Quando o atendente terminar, seu painel volta o estado do contato para `inicio` e o bot reassume. Se vários atendentes dividem o mesmo número, veja [multiatendimento no mesmo número](/blog/multiatendimento-mesmo-numero-whatsapp).

## Checklist antes de colocar no ar

- **Idempotência**: o mesmo evento pode chegar duas vezes. Guarde os `id` já processados — veja [webhook em produção](/blog/webhook-producao-assinatura-retry-idempotencia).
- **Estado persistente**: o `Map` em memória some no deploy. Use Redis ou banco.
- **Saída sempre visível**: toda tela do bot precisa de um caminho para humano.
- **Palavra de saída**: se alguém escrever "sair" ou "parar", respeite e registre — [opt-out](/blog/lista-contatos-optin-optout-whatsapp) vale para bot também.
- **Tempo de sessão**: se o cliente some por horas no meio de um fluxo, volte para `inicio` na próxima mensagem.

## Bot e bloqueio: o que realmente importa

Um bot que **responde** a quem escreveu é o uso mais seguro possível da API não oficial — e é por isso que a taxa de bloqueio nesse cenário é muito baixa quando o número é usado do jeito certo. Além do comportamento, a WAME protege cada instância com identidade de dispositivo própria e coerente com o país do número, limites de ritmo, reconexão espaçada e um monitor de saúde que pausa envios ao primeiro sinal de problema.

Onde o bot vira risco é quando ele passa a **iniciar** conversas em massa com quem nunca pediu. Isso é spam, e a WAME não apoia. Se o seu projeto precisa de campanhas, comece pela [lista com opt-in](/blog/lista-contatos-optin-optout-whatsapp) e pelo [controle de ritmo](/blog/fila-rate-limit-retry-disparo-whatsapp).

## Conclusão

Criar um bot de WhatsApp com a API não oficial é juntar quatro peças: conexão por QR Code, webhook no formato `meta`, uma máquina de estados com uma função por etapa e respostas com lista, botões e texto livre. Some a isso "digitando...", passagem para humano e idempotência, e o bot está pronto para produção — sem aprovação da Meta, sem template e sem cobrança por mensagem. Quando as perguntas ficarem abertas demais para um menu, é hora de plugar IA na mesma estrutura. Todos os endpoints estão na [documentação](/docs).

## Perguntas frequentes

### Como criar um bot de WhatsApp com a API não oficial?

Conecte seu número a uma instância da WAME por QR Code ou código de pareamento, configure a URL do seu servidor como webhook e, a cada mensagem recebida, decida a resposta com uma máquina de estados. As respostas saem por endpoints como /message/text, /message/list e /message/button_reply.

### Preciso de aprovação da Meta ou de templates para o bot?

Não. Na API não oficial o bot envia texto livre, listas e botões sem cadastro de app na Meta e sem templates pré-aprovados. A camada não oficial não é afiliada ao WhatsApp e o uso é de responsabilidade de quem opera o número.

### Por que meus botões não aparecem na primeira mensagem?

O WhatsApp do destinatário não renderiza mensagens interativas (botões e listas) na primeira mensagem de uma conversa que nunca existiu. A solução é abrir a conversa com um texto antes do menu: uma mensagem normal já basta, e em conversas iniciadas pelo cliente o problema não aparece.

### O bot precisa ser feito com inteligência artificial?

Não. Um bot de regras com menu resolve a maior parte do atendimento repetitivo (segunda via, horário, status de pedido) de forma previsível e barata. IA entra quando as perguntas são abertas demais para um menu.

### Como passo a conversa do bot para um atendente?

Tenha um estado 'humano' na máquina de estados. Quando o cliente escolhe falar com alguém, o bot avisa, marca a conversa e para de responder aquele número até o atendente encerrar o atendimento.
