> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ararahq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Ruby

> SDK Ruby oficial da Arara (gem ararahq): só biblioteca padrão, respostas em Hash, retry automático e exceções por status.

## Instalar

```ruby theme={null}
# Gemfile
gem "ararahq"
```

```bash theme={null}
bundle install
```

Versão no RubyGems: `1.0.0`. Requer Ruby 3.0+. Sem dependências de runtime (`net/http`, `json`, `uri`). Testado no Ruby 4.0.

## Configurar

```ruby theme={null}
require "arara"

client = Arara::Client.new(
  api_key: ENV.fetch("ARARA_API_KEY"),
  base_url: "https://api.ararahq.com", # opcional, este é o default
  timeout: 10,                          # opcional, em segundos (open e read)
  max_retries: 3                        # opcional
)
```

Sem `api_key`, o construtor lança `ArgumentError`.

As respostas voltam como `Hash` com chaves `String`, exatamente como a API devolve. As listas (`templates.list`, `campaigns.list`, `smart_links.list`, `wallet.transactions`) voltam como `Arara::Page`: `page.data` (Array), `page.pagination` (`page`, `size`, `total_elements`, `total_pages`), `page.next_page?` e `each` (inclui `Enumerable`).

## Enviar a primeira mensagem

Você precisa de plano assinado, número conectado, um template aprovado e saldo. Veja [Antes de começar](/quickstart#antes-de-começar). Troque `pedido_confirmado` pelo nome do seu template.

Salve como `enviar.rb`:

```ruby theme={null}
require "arara"

client = Arara::Client.new(api_key: ENV.fetch("ARARA_API_KEY"))

order_id = "12345" # id do evento no seu sistema

result = client.messages.send_message(
  receiver: "+5511999999999",
  template_name: "pedido_confirmado",
  template_variables: ["João", order_id],
  idempotency_key: "hello-#{order_id}"
)

puts result["id"]     # ara_msg_...
puts result["status"] # PENDING
```

Rode:

```bash theme={null}
ARARA_API_KEY=ara_live_... bundle exec ruby enviar.rb
```

`send_message` tem o alias `deliver`. O telefone aceita `+5511999999999`, `whatsapp:+5511999999999` ou `5511999999999`.

Com variáveis no corpo do template e mídia no header:

```ruby theme={null}
client.messages.send_message(
  receiver: "+5511999999999",
  template_name: "confirmacao_pedido",
  template_variables: [
    "https://meusite.com/nota/123.pdf", # header (mídia opcional)
    "João",                              # {{1}}
    "ARA-12345"                          # {{2}}
  ],
  idempotency_key: "pedido-12345"
)
```

Para responder dentro da janela de 24h, mande `body:` em vez de `template_name:`:

```ruby theme={null}
client.messages.send_message(
  receiver: "+5511999999999",
  body: "Oi! Vi sua dúvida e respondo já já.",
  idempotency_key: "resposta-98765"
)
```

Consulte uma mensagem pelo id que o envio devolveu:

```ruby theme={null}
message = client.messages.get(result["id"])
puts message["status"] # DELIVERED, FAILED...
```

Para acompanhar o status sem consultar, use o webhook `message.status_updated`.

Para enviar o mesmo template a vários números de uma vez (até 1000), use `client.messages.send_batch(template_name:, messages:)`.

## Templates

`get`, `get_status` e `delete` recebem o **id** do template (UUID), não o nome. Para achar o id pelo nome, use `find_by_name`.

```ruby theme={null}
page = client.templates.list
page.each { |t| puts "#{t["name"]} #{t["providerStatus"]}" }

template = client.templates.create(
  "name" => "promo_natal",
  "category" => "MARKETING",
  "language" => "pt_BR",
  "body" => "Oi {{1}}, oferta de Natal: {{2}}% off até {{3}}.",
  "samples" => { "1" => "João", "2" => "30", "3" => "sexta" }
)

status = client.templates.get_status(template["id"])
puts status["status"]

existing = client.templates.find_by_name("pedido_confirmado") # Hash, ou nil se não existir
```

## Tratar erros

```ruby theme={null}
begin
  client.messages.send_message(
    receiver: "+5511999999999",
    template_name: "confirmacao_pedido",
    idempotency_key: "pedido-12345"
  )
rescue Arara::RateLimitError => e
  sleep(e.retry_after || 1)
rescue Arara::PaymentRequiredError
  puts "Saldo insuficiente. Recarregue em https://app.ararahq.com."
rescue Arara::UnprocessableEntityError => e
  puts "Telefone ou dados inválidos: #{e.code}"
rescue Arara::Error => e
  puts "#{e.status_code} #{e.code}: #{e.message}"
end
```

| Exceção | Quando |
| - | - |
| `Arara::BadRequestError` | 400 |
| `Arara::AuthenticationError` | 401, e 403 sem `error.code` |
| `Arara::PaymentRequiredError` | 402 |
| `Arara::PermissionError` | 403 com `error.code` |
| `Arara::PlanFeatureLockedError` | 403 `PLAN_FEATURE_LOCKED` (subclasse de `PermissionError`; `feature`, `current_plan`, `upgrade_to`) |
| `Arara::NotFoundError` | 404 |
| `Arara::ConflictError` | 409 |
| `Arara::UnprocessableEntityError` | 422 |
| `Arara::RateLimitError` | 429 (`retry_after` em segundos) |
| `Arara::ServerError` | 5xx |
| `Arara::NetworkError` | timeout ou falha de conexão, depois dos retries |
| `Arara::Error` | base de todas; qualquer outro status |

Todas expõem `status_code`, `code` (`error.code` da API), `details` e `retry_after`. Em 401 e 403 de autenticação a API não devolve o envelope de erro, então `code` e `details` vêm `nil` e `message` traz o texto genérico do SDK. Lista de códigos em [Erros](/erros).

## Retentativas e idempotência

O SDK retenta até `max_retries` vezes em timeout, falha de conexão, 429 e qualquer 5xx. Respeita `Retry-After` (segundos ou data, até 30s); sem ele, espera 0,5s e dobra a cada tentativa, até 30s. Para desligar: `max_retries: 0`.

`messages.send_message`, `messages.send_batch` e `campaigns.create` mandam sempre um `Idempotency-Key` e repetem a mesma chave em todas as tentativas. Se você não passar `idempotency_key:`, o SDK gera um UUID. Requisições `POST` e `PATCH` sem chave não são retentadas.

<Warning>
  A chave gerada vale só dentro de uma chamada. Se o seu processo cair e rodar de novo, o envio sai duplicado. Passe `idempotency_key:` derivada de um id do seu sistema (id do pedido, id da tentativa), não de um valor novo a cada chamada.
</Warning>

Repetir a mesma `idempotency_key` devolve a mensagem original. No envio de mensagem, a API não compara o corpo: reusar a chave com outro corpo devolve a mensagem original, sem novo envio.

Passe a sua chave também em campanha, para que um retry do seu lado não crie campanha duplicada:

```ruby theme={null}
campaign = client.campaigns.create(
  {
    "name" => "blackfriday-2026",
    "templateName" => "bf_oferta_geral",
    "contacts" => [
      { "to" => "+5511999999999", "variables" => ["João", "30"] },
      { "to" => "+5511888888888", "variables" => ["Maria", "25"] }
    ]
  },
  idempotency_key: "bf-2026-lote-1"
)

detail = client.campaigns.get(campaign["id"])
```

## Webhooks

O SDK não tem helper de webhook. Valide `X-Arara-Signature` com HMAC-SHA256 de `"#{timestamp}.#{raw_body}"`, prefixado com `sha256=`, e compare em tempo constante. Recuse timestamp a mais de 5 minutos do seu relógio. Detalhes em [Webhooks](/webhooks).

```ruby theme={null}
require "openssl"

TOLERANCE_SECONDS = 300

def valid_arara_signature?(raw_body, timestamp, signature, secret)
  return false unless timestamp.is_a?(String) && signature.is_a?(String)
  return false unless timestamp.match?(/\A\d+\z/) && signature.start_with?("sha256=")
  return false if (Time.now.to_i - timestamp.to_i).abs > TOLERANCE_SECONDS

  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{raw_body}")
  received = signature.delete_prefix("sha256=")
  expected.bytesize == received.bytesize && OpenSSL.fixed_length_secure_compare(expected, received)
end
```

Passe `raw_body` como string (`request.raw_post` no Rails, `request.body.read` no Rack), antes de qualquer parse de JSON.

## Recursos disponíveis

| Recurso | Acesso | Métodos |
| - | - | - |
| Messages | `client.messages` | `send_message(receiver:, template_name:, template_variables:, body:, media_url:, scheduled_at:, idempotency_key:, ...)`, `send_batch`, `get(id)`, `list_by_batch` |
| Templates | `client.templates` | `list`, `find_by_name`, `create(payload)`, `get(id)`, `get_status(id)`, `delete(id)`, `analytics`, `template_analytics` |
| Campaigns | `client.campaigns` | `create(payload, idempotency_key:)`, `list(page:, size:, status:)`, `estimate(template_name:, count:)`, `get(id)`, `cancel(id)` |
| Contacts | `client.contacts` | `list`, `import_batch`, `stats`, `reactivation_candidates`, `list_tags`, `get`, `update`, `messages` |
| Conversations | `client.conversations` | `list`, `lead_stats`, `messages`, `reply(conversation_id:, body:)`, `update_status`, `window_status` |
| Wallet | `client.wallet` | `transactions`, `get_auto_recharge`, `update_auto_recharge` |
| Numbers | `client.numbers` | `list`, `update`, `delete`, `request`, `list_requests`, `sync`, `warming` |
| Smart links | `client.smart_links` | `create`, `update`, `list`, `stats` |
| Opt-outs | `client.opt_outs` | `list`, `create(phone:, reason:)`, `get(phone)`, `delete(phone)` (exigem chave ADMIN) |
| Auth | `client.auth` | `me` (exige chave ADMIN) |

## Limitações conhecidas

| Método | Problema |
| - | - |
| Webhook | Sem helper de validação; use o código da seção Webhooks. A URL do webhook só se configura pelo painel. |
| Chaves de API | Sem recurso de gestão de chaves: a API recusa gestão de chaves autenticada por chave (403). Crie e revogue pelo painel. |
| `templates.get/get_status/delete` | Precisam do id (UUID). Com o nome a API responde 400. |

## Próximos passos

<CardGroup cols={2}>
  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Endpoints REST por baixo do SDK.
  </Card>

  <Card title="Webhooks" icon="bell" href="/webhooks">
    Eventos, payload e validação de assinatura.
  </Card>
</CardGroup>

Código-fonte: [github.com/ararahq/arara-ruby-sdk](https://github.com/ararahq/arara-ruby-sdk).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.