Como enviar WhatsApp com Python usando a APIBrasil: guia completo

Capa do artigo Como enviar WhatsApp com Python usando a APIBrasil: guia completo

Enviar WhatsApp com Python é uma das integrações mais pedidas por times de produto: confirmação de pedido, lembrete de consulta, código de verificação, aviso de boleto vencendo. Com a APIBrasil, isso vira uma chamada HTTP feita com a biblioteca requests, sem automatizar o WhatsApp Web na mão e sem manter um navegador aberto no seu servidor.

O tutorial parte do zero: você cria a conta, gera o Bearer Token, conecta um dispositivo e faz a primeira chamada. Depois evolui para imagem e arquivo, aprende a ler o campo error (que pode vir com HTTP 200) e acompanha o status de entrega. No fim, sai com um script reutilizável para colocar no seu projeto.

Todo o código roda em Python 3.10 ou superior. Os endpoints foram conferidos na documentação oficial em doc.apibrasil.io; quando um campo opcional não aparece na doc, o texto avisa para você confirmar antes de usar em produção.

O que você vai construir

O resultado é um módulo para enviar WhatsApp com Python com três funções: enviar texto, enviar arquivo (imagem, PDF ou áudio) e verificar se o dispositivo está conectado. Ele lê as credenciais de variáveis de ambiente, reaproveita a conexão HTTP entre chamadas e devolve o campo data da resposta ou lança uma exceção com a mensagem que a API retornou.

O fluxo tem cinco etapas, e cada seção cobre uma delas:

  1. Gerar o Bearer Token no painel e guardá-lo em variável de ambiente.
  2. Criar um dispositivo WhatsApp, obter o DeviceToken e conectar o número via QR Code.
  3. Fazer o POST em /whatsapp/sendText com requests.
  4. Tratar o campo error e os códigos HTTP de autenticação e saldo.
  5. Acompanhar o status de entrega (enviado, recebido, lido) pelos webhooks do dispositivo.

Se o seu time trabalha com JavaScript, existe um guia equivalente para criar um bot de WhatsApp com Node.js no Programador Brasil. A autenticação e os endpoints são os mesmos; muda só a linguagem.

Pré-requisitos: conta, token e dispositivo

Você precisa de três coisas antes de escrever código: uma conta na APIBrasil, um Bearer Token e um dispositivo WhatsApp conectado. As APIs de WhatsApp são cobradas por plano, e não por crédito, e exigem o header DeviceToken além do Authorization.

1. Conta e Bearer Token

Crie a conta em platform.apibrasil.com.br e gere o token no painel. Ele vai no header Authorization: Bearer em toda chamada, de qualquer API do catálogo. Se esta é sua primeira integração, o guia de primeiros passos com Bearer Token e homologação mostra o painel tela por tela.

2. Dispositivo e DeviceToken

No painel, contrate um plano de WhatsApp e crie um dispositivo. Cada dispositivo tem o próprio DeviceToken, que identifica qual número vai disparar a mensagem. Sem esse header, a API recusa a requisição, mesmo com o Bearer Token certo.

3. Conectar o número

Com o dispositivo criado, inicie a sessão com POST /whatsapp/start, informando um nome em session e qrcode: true. A resposta traz o QR Code para ler no celular, em Dispositivos conectados. Quando o status muda para inChat, o número está pronto para enviar e receber. Se preferir código de pareamento em vez de QR Code, envie o telefone no campo number.

4. Ambiente Python

python -m venv .venv && source .venv/bin/activate
pip install requests python-dotenv

# .env (nunca versione este arquivo)
APIBRASIL_TOKEN=seu_bearer_token
APIBRASIL_DEVICE_TOKEN=token_do_dispositivo

Tokens nunca entram no código-fonte. Leia-os com os.environ e mantenha o .env fora do repositório; em produção, use o cofre de segredos da sua plataforma.

Primeira chamada: enviar WhatsApp com Python e requests

Toda chamada à APIBrasil é um POST com JSON para a base https://gateway.apibrasil.io/api/v2. Para texto, o endpoint é /whatsapp/sendText e o corpo tem dois campos obrigatórios: number (telefone só com dígitos, com código do país e DDD) e text (a mensagem).

import os

import requests
from dotenv import load_dotenv

load_dotenv()

BASE_URL = "https://gateway.apibrasil.io/api/v2"
HEADERS = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {os.environ['APIBRASIL_TOKEN']}",
    "DeviceToken": os.environ["APIBRASIL_DEVICE_TOKEN"],
}


def enviar_texto(numero: str, texto: str) -> dict:
    """Envia texto. numero no formato 55 + DDD + telefone, só dígitos."""
    resposta = requests.post(
        f"{BASE_URL}/whatsapp/sendText",
        headers=HEADERS,
        json={"number": numero, "text": texto},
        timeout=30,
    )
    corpo = resposta.json()
    # A API pode responder HTTP 200 com error = true: confira sempre o campo.
    if resposta.status_code != 200 or corpo.get("error"):
        raise RuntimeError(f"Falha no envio: {corpo.get('message')}")
    return corpo


if __name__ == "__main__":
    print(enviar_texto("5531999999999", "Olá! Seu pedido #1234 foi aprovado."))

A resposta segue o padrão de todas as APIs da plataforma: error (booleano), message (texto legível), data (o retorno da operação) e, nas APIs por crédito, balance e tax. O detalhe que mais derruba integração é o erro vir com HTTP 200: por isso o código testa resposta.status_code e corpo[“error”] juntos, e nunca só um deles.

Antes de um disparo em lote, confira se o dispositivo está online com POST /whatsapp/isConnected (sem corpo, mesmos headers). Se ele responder que não há conexão, não adianta enfileirar mensagens; o número precisa ser reconectado pelo QR Code.

Infográfico com os cinco passos para enviar WhatsApp com Python usando a APIBrasil: gerar o token, conectar o dispositivo, fazer o POST com requests, tratar o campo error e acompanhar o status de entrega

Enviando texto, imagem e arquivo

A API WhatsApp WPP tem um endpoint para cada tipo de conteúdo. Para a maior parte dos casos, dois resolvem: sendText para texto e sendFile para qualquer mídia hospedada em uma URL pública (imagem, PDF, planilha). A tabela resume o que foi confirmado na documentação:

EndpointCampos do corpoUso
POST /whatsapp/sendTextnumber, text (opcionais: time_typing, options)Texto simples; time_typing simula digitação em ms
POST /whatsapp/sendFilenumber, pathImagem, documento ou vídeo a partir de uma URL pública
POST /whatsapp/sendFile64confira na documentaçãoMesmo envio, com o arquivo em base64
POST /whatsapp/sendAudionumber, pathÁudio a partir de URL
POST /whatsapp/sendText/queueigual ao sendTextEnvio assíncrono; devolve o id de um job

Como os endpoints compartilham headers e formato de resposta, vale centralizar a chamada em uma função genérica e deixar cada tipo de envio como um atalho:

def chamar(endpoint: str, payload: dict) -> dict:
    """POST genérico: valida HTTP e o campo error da APIBrasil."""
    resposta = requests.post(
        f"{BASE_URL}/whatsapp/{endpoint}", headers=HEADERS, json=payload, timeout=60
    )
    corpo = resposta.json()
    if resposta.status_code != 200 or corpo.get("error"):
        raise RuntimeError(f"{endpoint}: {corpo.get('message', resposta.text)}")
    return corpo


def enviar_arquivo(numero: str, url_publica: str) -> dict:
    """Imagem, PDF ou vídeo. A URL precisa ser acessível pela internet."""
    return chamar("sendFile", {"number": numero, "path": url_publica})


def enviar_audio(numero: str, url_publica: str) -> dict:
    return chamar("sendAudio", {"number": numero, "path": url_publica})


# exemplos
enviar_arquivo("5531999999999", "https://meusite.com.br/notas/nf-1234.pdf")
enviar_arquivo("5531999999999", "https://meusite.com.br/img/promocao.jpg")

Três cuidados práticos. Primeiro, a URL em path tem que responder sem autenticação, porque é o servidor da APIBrasil que baixa o arquivo, não o seu script. Segundo, para arquivos gerados na hora (um PDF montado no request, por exemplo), use sendFile64 e confira na documentação o nome exato dos campos do base64. Terceiro, campos extras como legenda e nome de arquivo não aparecem na doc do sendFile; se precisar deles, teste no seu próprio número antes de assumir que funcionam.

Tratando erros e status de entrega

Um envio que “deu certo” na resposta HTTP significa apenas que a API aceitou a mensagem. Entre isso e o cliente ler o texto existem três pontos de falha: credenciais, saldo ou plano, e a entrega em si pelo WhatsApp.

Erros de autenticação, plano e servidor

  • 401 ou 403: Bearer Token inválido, expirado ou DeviceToken faltando. Corrija a configuração; repetir a chamada não resolve.
  • 402: saldo ou plano insuficiente. Também não adianta repetir; é preciso regularizar no painel.
  • HTTP 200 com error: true: número mal formatado, dispositivo desconectado ou outro erro de negócio. A explicação está em message.
  • 5xx ou timeout: instabilidade. Aqui sim vale repetir, com espera exponencial e poucas tentativas.

Nunca repita automaticamente respostas 4xx: além de não resolver, em APIs cobradas por chamada cada tentativa pode gerar custo. A política de retry abaixo segue essa regra.

import time


def chamar_com_retry(endpoint: str, payload: dict, tentativas: int = 3) -> dict:
    for i in range(tentativas):
        try:
            resposta = requests.post(
                f"{BASE_URL}/whatsapp/{endpoint}", headers=HEADERS, json=payload, timeout=30
            )
        except requests.Timeout:
            time.sleep(2 ** i)  # 1s, 2s, 4s
            continue
        if resposta.status_code >= 500:
            time.sleep(2 ** i)
            continue
        corpo = resposta.json()
        if resposta.status_code != 200 or corpo.get("error"):
            raise RuntimeError(corpo.get("message", resposta.text))  # 4xx: não repete
        return corpo
    raise RuntimeError(f"{endpoint}: sem resposta após {tentativas} tentativas")

Status de entrega: enviado, recebido, lido

O status de entrega não vem na resposta do sendText; ele chega por webhook. Ao iniciar o dispositivo, você informa uma URL em wh_message, e a APIBrasil passa a enviar um POST a cada evento: SEND_MESSAGE quando a mensagem sai, MESSAGE_STATUS quando o WhatsApp confirma cada etapa (SENT, RECEIVED, READ, PLAYED para áudio) ou reporta falha (FAILED, EXPIRED, entre outros). O campo id do evento é o mesmo do envio, o que permite cruzar as duas pontas.

Se você usou a rota /queue, a resposta traz o id de um job. O endpoint GET /jobs lista os jobs da conta com status, created_at e processed_at; o resultado do envio em si continua chegando pelos eventos do dispositivo. Para a parte de servidor que recebe esses eventos, veja o tutorial de webhooks de WhatsApp e SMS com Node.js deste blog.

Depois que o envio está estável, o problema muda de lado: quem responde o cliente que respondeu? Vale monitorar o atendimento no WhatsApp com indicadores de tempo de resposta e conversas sem retorno, porque uma notificação bem entregue que gera uma pergunta sem resposta vira reclamação.

Script completo

Juntando tudo em uma classe para enviar WhatsApp com Python: sessão HTTP reaproveitada, retry só para 5xx e timeout, validação do campo error e os três envios. Salve como apibrasil_whatsapp.py e importe no seu projeto.

"""apibrasil_whatsapp.py - cliente mínimo para a API WhatsApp WPP da APIBrasil."""
import os
import re
import time

import requests


class ErroAPIBrasil(Exception):
    pass


class WhatsAppAPIBrasil:
    BASE_URL = "https://gateway.apibrasil.io/api/v2/whatsapp"

    def __init__(self, token: str | None = None, device_token: str | None = None):
        self.sessao = requests.Session()
        self.sessao.headers.update({
            "Content-Type": "application/json",
            "Authorization": f"Bearer {token or os.environ['APIBRASIL_TOKEN']}",
            "DeviceToken": device_token or os.environ["APIBRASIL_DEVICE_TOKEN"],
        })

    @staticmethod
    def normalizar(numero: str) -> str:
        digitos = re.sub(r"\D", "", numero)
        return digitos if digitos.startswith("55") else f"55{digitos}"

    def _post(self, endpoint: str, payload: dict | None = None, tentativas: int = 3) -> dict:
        for i in range(tentativas):
            try:
                r = self.sessao.post(f"{self.BASE_URL}/{endpoint}", json=payload or {}, timeout=30)
            except requests.Timeout:
                time.sleep(2 ** i)
                continue
            if r.status_code >= 500:
                time.sleep(2 ** i)
                continue
            corpo = r.json()
            if r.status_code != 200 or corpo.get("error"):
                raise ErroAPIBrasil(f"{endpoint} ({r.status_code}): {corpo.get('message', r.text)}")
            return corpo
        raise ErroAPIBrasil(f"{endpoint}: sem resposta após {tentativas} tentativas")

    def conectado(self) -> bool:
        corpo = self._post("isConnected")
        return not corpo.get("error")

    def texto(self, numero: str, texto: str) -> dict:
        return self._post("sendText", {"number": self.normalizar(numero), "text": texto})

    def arquivo(self, numero: str, url: str) -> dict:
        return self._post("sendFile", {"number": self.normalizar(numero), "path": url})

    def audio(self, numero: str, url: str) -> dict:
        return self._post("sendAudio", {"number": self.normalizar(numero), "path": url})


if __name__ == "__main__":
    wa = WhatsAppAPIBrasil()
    if not wa.conectado():
        raise SystemExit("Dispositivo desconectado: leia o QR Code novamente.")
    wa.texto("(31) 99999-9999", "Olá! Sua consulta é amanhã às 14h.")
    wa.arquivo("(31) 99999-9999", "https://meusite.com.br/docs/orientacoes.pdf")

Para rodar, exporte as duas variáveis de ambiente, ajuste o número de teste para o seu e execute python apibrasil_whatsapp.py. O método conectado() usa o próprio campo error como sinal; se a sua versão da API devolver o estado em data, adapte a leitura conforme a resposta que você observar.

Perguntas frequentes

Preciso da API oficial do WhatsApp para usar esse tutorial?

Não. O tutorial usa a API WhatsApp WPP da APIBrasil, que conecta um número comum pelo QR Code, do mesmo jeito que o WhatsApp Web. A plataforma também oferece a API Oficial (Cloud) para quem precisa de conta verificada e templates aprovados; os conceitos de token e resposta são os mesmos.

Como devo formatar o número de destino?

Só dígitos, com código do país e DDD: 55 + DDD + telefone, como 5531999999999. Sem o +, sem parênteses e sem hífen. A função normalizar() do script faz essa limpeza e adiciona o 55 quando ele falta.

A resposta veio com error: false, mas a mensagem não chegou. O que verificar?

Primeiro, chame /whatsapp/isConnected para confirmar que o dispositivo está online. Depois, acompanhe os eventos MESSAGE_STATUS no webhook: se o status parou em SENT, o celular do destinatário pode estar sem internet; se veio FAILED, o número pode não ter WhatsApp ativo. Testar com o seu próprio número elimina boa parte das dúvidas.

Posso enviar para uma lista grande de números?

Pode, mas com cuidado. Use a rota /queue para não bloquear o script, respeite o limite do seu plano e envie apenas para quem autorizou receber mensagens. Disparos em massa sem opt-in levam ao bloqueio do número pelo próprio WhatsApp, e nenhum retry resolve isso.

Dá para testar sem gastar?

A homologação com homolog: true vale para as APIs de consulta por crédito, como CPF e CNPJ. No WhatsApp, o jeito seguro é enviar para o seu próprio número a partir do dispositivo de teste. Mantenha um dispositivo separado para desenvolvimento, assim um bug não dispara mensagens para clientes reais.

Próximos passos

Com o módulo pronto, o próximo passo natural é receber as respostas dos clientes e reagir a elas, o que pede um servidor de webhooks e, muitas vezes, um chatbot com menu. Se parte do time usa JavaScript, a versão deste guia para enviar WhatsApp com Node.js usa os mesmos endpoints. E se ainda não tem conta, crie a sua em platform.apibrasil.com.br, conecte um dispositivo de teste e rode o script acima com o seu número antes de ligar qualquer fluxo em produção.

Loading

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *