Dev

Enviar WhatsApp con Ruby: ejemplo de API REST (Net::HTTP y Rails)

Para enviar WhatsApp con Ruby haces un POST con Net::HTTP a /api/v1/{instanceId}/send/text, con los encabezados x-api-key y x-api-secret y un JSON con number y text. Net::HTTP viene en la biblioteca estándar, así que no hay gem que instalar. Abajo: texto, multimedia, retry, un job en Rails y un webhook en Sinatra.

Publicado el · 3 min de lectura

Este artículo también está disponible en: Português · English

Qué necesitas antes de empezar

Cuenta en ZapZap, instancia conectada por código QR, clave y secreto de la API y el ID de la instancia. La autenticación usa x-api-key y x-api-secret. Guarda las credenciales en ENV o en las credentials de Rails.

Prueba primero con cURL, para separar error de código de error de cuenta.

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."}'

Cómo enviar un mensaje de texto con Net::HTTP

La clase de abajo reutiliza la configuración, define open_timeout y read_timeout y lanza un error con estado y cuerpo cuando la respuesta no es 2xx. Usa JSON.generate para el cuerpo.

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.")

Cómo enviar imagen, PDF y audio con Ruby

La ruta es /send/media. El campo type define el tipo (image, video, audio, ptt, document, sticker y otros), file recibe la URL pública, text es el pie y docName el nombre del 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

Cómo hacer retry y enviar desde Rails con Active Job

Repite solo errores transitorios (429 y 5xx) con espera creciente. En Rails, usa un job: el retry_on de Active Job repite solo; el job solo deja subir el error en 429 y 5xx y apenas registra los 4xx, que nunca van a funcionar. Así la solicitud del usuario no espera a 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.")

Cómo recibir las respuestas con un webhook en Sinatra

Configura la URL en configurar webhook y activa el evento messages. El mensaje recibido está en data.message (text, chatid, fromMe). Ignora fromMe verdadero y responde 200 enseguida. En Rails, el equivalente es un controller con skip_forgery_protection.

La validación de la firma y la deduplicación siguen la guía de webhook en producción.

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

Lista de verificación antes de producción

Credenciales en ENV o credentials; timeouts de apertura y lectura; error con estado y cuerpo; retry solo para 429 y 5xx; envío por job; firma del webhook validada; y enviar solo a quien aceptó recibir.

Otros lenguajes: Python, Node.js, PHP, Go. Todos los endpoints están en la referencia de la API.

Resumen rápido

  • Net::HTTP de la biblioteca estándar basta: no hace falta ninguna gem.
  • Define open_timeout y read_timeout y lanza error con estado y cuerpo.
  • En Rails, envía por Active Job con retry_on para fallos transitorios.
  • En el webhook, ignora fromMe y responde 200 antes de procesar.

Lee también

Preguntas frecuentes

¿Puedo usar Faraday o HTTParty en lugar de Net::HTTP?

Sí. La API es REST: los encabezados x-api-key y x-api-secret y el JSON con number y text son los mismos en cualquier cliente HTTP.

¿Cómo guardo las credenciales en Rails?

En credentials cifradas o variables de entorno. Nunca en el repositorio ni en el front-end.

¿Se ejecutó el ejemplo contra la API?

El código usa los endpoints y campos de la referencia de la API. Ejecuta el cURL del inicio en tu instancia y luego el Ruby, y revisa la respuesta antes de pasar a producción.

¿Usar una API no oficial puede llevar al bloqueo del número?

Puede. Una API no oficial va contra los términos de WhatsApp y el número puede ser limitado o bloqueado. Opt-in, ritmo humano, calentamiento y lista limpia reducen el riesgo, pero no hay garantía. Para un canal homologado, usa la API oficial de Meta.

Ejecuta Ruby en tu instancia

Crea la cuenta, conecta un número y envía el primer mensaje. R$15 por número activo al mes, sin cobro por mensaje.