Dev

Enviar WhatsApp com Ruby: exemplo de API REST (Net::HTTP e Rails)

Para enviar WhatsApp com Ruby você faz um POST com Net::HTTP em /api/v1/{instanceId}/send/text, com os cabeçalhos x-api-key e x-api-secret e um JSON com number e text. Net::HTTP vem na biblioteca padrão, então não há gem a instalar. Abaixo: texto, mídia, retry, um job no Rails e um webhook em Sinatra.

Publicado em · 2 min de leitura

Este artigo também está disponível em: English · Español

O que você precisa antes de começar

Conta no ZapZap, instância conectada por QR code, chave e segredo da API e o ID da instância. A autenticação usa x-api-key e x-api-secret. Guarde as credenciais em ENV ou nas credentials do Rails.

Teste com cURL primeiro, para separar erro de código de erro de conta.

cURL

curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/send/text \
  -H "Content-Type: application/json" \
  -H "x-api-key: SUA_API_KEY" \
  -H "x-api-secret: SEU_API_SECRET" \
  -d '{"number":"5511999999999","text":"Olá! Teste de envio pela API."}'

Como enviar uma mensagem de texto com Net::HTTP

A classe abaixo reaproveita a configuração, define open_timeout e read_timeout e levanta um erro com status e corpo quando a resposta não é 2xx. Use JSON.generate para o corpo.

Ruby

require "net/http"
require "json"

class ZapZap
  class Erro < StandardError
    attr_reader :status, :corpo
    def initialize(status, corpo)
      @status, @corpo = status, corpo
      super("ZapZap #{status}: #{corpo}")
    end
  end

  def initialize(instancia: ENV.fetch("ZAPZAP_INSTANCE_ID"),
                 key: ENV.fetch("ZAPZAP_API_KEY"),
                 secret: ENV.fetch("ZAPZAP_API_SECRET"))
    @base = "https://api.zapzapapi.com/api/v1/#{instancia}"
    @headers = { "Content-Type" => "application/json",
                 "x-api-key" => key, "x-api-secret" => secret }
  end

  def enviar_texto(numero, texto)
    post("/send/text", number: numero, text: texto)
  end

  def post(caminho, dados)
    uri = URI(@base + caminho)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.open_timeout = 5
    http.read_timeout = 30
    r = http.post(uri.path, JSON.generate(dados), @headers)
    raise Erro.new(r.code.to_i, r.body) unless r.is_a?(Net::HTTPSuccess)
    JSON.parse(r.body)
  end
end

puts ZapZap.new.enviar_texto("5511999999999", "Olá! Seu pedido saiu para entrega.")

Como enviar imagem, PDF e áudio com Ruby

A rota é /send/media. O campo type define o tipo (image, video, audio, ptt, document, sticker e outros), file recebe a URL pública, text é a legenda e docName o nome do documento. Para PDF, type document.

Ruby

class ZapZap
  def enviar_pdf(numero, url, nome, legenda = "")
    post("/send/media", number: numero, type: "document",
                        file: url, docName: nome, text: legenda)
  end
end

Como fazer retry e enviar pelo Rails com Active Job

Repita só erros transitórios (429 e 5xx) com espera crescente. No Rails, use um job: o Active Job com retry_on repete sozinho; o job só deixa o erro subir para 429 e 5xx e apenas registra os 4xx, que nunca vão funcionar. Assim a requisição do usuário não espera o WhatsApp.

Ruby (Rails)

class EnviarWhatsappJob < ApplicationJob
  queue_as :default
  retry_on ZapZap::Erro, Net::OpenTimeout, Net::ReadTimeout,
           wait: :polynomially_longer, attempts: 4

  def perform(numero, texto)
    ZapZap.new.enviar_texto(numero, texto)
  rescue ZapZap::Erro => e
    raise if e.status == 429 || e.status >= 500 # o retry_on repete
    Rails.logger.error(e.message)               # 4xx: repetir não resolve, só registra
  end
end

# Uso: EnviarWhatsappJob.perform_later("5511999999999", "Seu pedido foi enviado.")

Como receber as respostas com um webhook em Sinatra

Configure a URL em configurar webhook e ligue o evento messages. A mensagem recebida está em data.message (text, chatid, fromMe). Ignore fromMe verdadeiro e responda 200 logo. No Rails, o equivalente é um controller com skip_forgery_protection.

A validação da assinatura e a deduplicação seguem o guia de webhook em produção.

Ruby (Sinatra)

require "sinatra"
require "json"

post "/webhooks/zapzap" do
  evento = JSON.parse(request.body.read)
  msg = evento.dig("data", "message")
  if evento["event"] == "messages" && msg && !msg["fromMe"]
    Thread.new { puts "mensagem de #{msg["chatid"]}: #{msg["text"]}" }
  end
  status 200
end

Checklist antes de colocar em produção

Credenciais em ENV ou credentials; timeouts de abertura e leitura; erro com status e corpo; retry só para 429 e 5xx; envio por job; assinatura do webhook validada; e envio só para quem aceitou receber.

Outras linguagens: Python, Node.js, PHP, Go. Endpoints completos na referência da API.

Resumo rápido

  • Net::HTTP da biblioteca padrão basta: nenhuma gem é necessária.
  • Defina open_timeout e read_timeout e levante erro com status e corpo.
  • No Rails, envie por Active Job com retry_on para falhas transitórias.
  • No webhook, ignore fromMe e responda 200 antes de processar.

Perguntas frequentes

Posso usar Faraday ou HTTParty no lugar do Net::HTTP?

Pode. A API é REST: os cabeçalhos x-api-key e x-api-secret e o JSON com number e text são os mesmos em qualquer cliente HTTP.

Como guardar as credenciais no Rails?

Em credentials criptografadas ou variáveis de ambiente. Nunca no repositório nem no front-end.

O exemplo foi executado contra a API?

O código usa os endpoints e campos da referência da API. Rode o cURL do início na sua instância e depois o Ruby, e confira a resposta antes de ir para produção.

Usar a API não oficial pode levar a bloqueio do número?

Pode. Uma API não oficial viola os termos do WhatsApp e o número pode ser limitado ou bloqueado. Opt-in, ritmo humano, aquecimento e lista limpa reduzem o risco, sem garantia. Para canal homologado, use a API oficial da Meta.

Rode o Ruby na sua instância

Crie a conta, conecte um número e envie a primeira mensagem. R$15 por número ativo ao mês, sem cobrança por mensagem.