> ## 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 Node.js / TypeScript

> SDK oficial Node/TypeScript da Arara (@ararahq/sdk): envio de mensagens, templates, campanhas, contatos, retry automático e erro tipado.

## Instalar

```bash theme={null}
npm install @ararahq/sdk
```

[![npm](https://img.shields.io/npm/v/@ararahq/sdk)](https://www.npmjs.com/package/@ararahq/sdk)

Versão publicada: **2.0.0**. Pacote CommonJS (`require`) com build ESM (`import`) e tipos `.d.ts`. Depende de `axios`. O pacote não declara versão mínima de Node; o build de publicação roda em Node 20, então use Node 18 ou superior.

## Configurar

```typescript theme={null}
import { NodeSDK } from '@ararahq/sdk';

const sdk = new NodeSDK({
  apiKey: process.env.ARARA_API_KEY!,
  baseUrl: 'https://api.ararahq.com', // opcional, este é o default
  timeout: 10000,                     // opcional, em ms, default 10000
  maxRetries: 3,                      // opcional, default 3
});
```

A `apiKey` é obrigatória (o construtor lança `Error` sem ela) e usa o prefixo `ara_live_`. A Arara não tem modo de teste.

## 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.js`:

```javascript theme={null}
const { NodeSDK } = require('@ararahq/sdk');

const sdk = new NodeSDK({ apiKey: process.env.ARARA_API_KEY });

async function main() {
  const orderId = '12345'; // id do evento no seu sistema

  const response = await sdk.messages.send(
    {
      receiver: 'whatsapp:+5511999999999',
      templateName: 'pedido_confirmado',
      templateVariables: ['João', orderId],
    },
    { idempotencyKey: `hello-${orderId}` },
  );

  console.log(response.id);     // ara_msg_...
  console.log(response.status); // status inicial da mensagem
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
```

Rode:

```bash theme={null}
ARARA_API_KEY=ara_live_... node enviar.js
```

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

```javascript theme={null}
await sdk.messages.send({
  receiver: 'whatsapp:+5511999999999',
  templateName: 'confirmacao_pedido',
  templateVariables: [
    'https://meusite.com/nota/123.pdf', // header (mídia opcional)
    'João',                              // {{1}}
    'ARA-12345',                         // {{2}}
  ],
});
```

`variables` é aceito como apelido de `templateVariables`.

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

```javascript theme={null}
await sdk.messages.send({
  receiver: 'whatsapp:+5511999999999',
  body: 'Oi! Vi sua dúvida e respondo já já.',
});
```

Para consultar uma mensagem depois do envio, use `sdk.messages.get(response.id)`. O status também chega pelo webhook `message.status_updated`.

Para mandar o mesmo template a até 1000 destinatários, use `sendBatch`:

```javascript theme={null}
await sdk.messages.sendBatch({
  templateName: 'pedido_confirmado',
  messages: [{ receiver: '+5511999999999', variables: ['João', '12345'] }],
});
```

## Templates

Os métodos `get`, `getStatus`, `delete` e `analytics` recebem o **id** do template (UUID), não o nome. Para achar um template pelo nome, filtre a lista: `sdk.templates.list({ name: 'promo_natal' })`.

```typescript theme={null}
// Criar
const created = await sdk.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 de aprovação
const status = await sdk.templates.getStatus(created.id);
console.log(status.status, status.rejectionReason);

// Remover
await sdk.templates.delete(created.id);
```

`samples` é um objeto com o nome da variável como chave (`'1'` para `{{1}}`).

**Listar:** `list` devolve `{ data, pagination }` e aceita `page`, `size`, `name` e `status`.

```typescript theme={null}
const { data, pagination } = await sdk.templates.list({ page: 0, size: 50 });
console.log(data.map((t) => t.name), pagination.totalPages);
```

## Tratar erros

Toda falha lança `AraraError`. Duas subclasses cobrem os casos comuns: `AuthenticationError` (401, ou 403 sem código de erro: chave inválida, expirada, sem permissão ou fora da allowlist de IP) e `PlanFeatureLockedError` (403 `PLAN_FEATURE_LOCKED`, com `feature`, `currentPlan` e `upgradeTo`).

```typescript theme={null}
import { AraraError } from '@ararahq/sdk';

try {
  await sdk.messages.send({ receiver: 'whatsapp:+5511999999999', templateName: 'confirmacao_pedido' });
} catch (err) {
  if (err instanceof AraraError) {
    console.error(err.statusCode, err.code, err.message, err.details);
    if (err.statusCode === 429) {
      console.log('Tente de novo em', err.retryAfter, 'segundos');
    }
  }
}
```

| Campo | Conteúdo |
| - | - |
| `statusCode` | Status HTTP. Ausente em falha de rede. |
| `code` | `error.code` da API. `NETWORK_ERROR` sem resposta, `UNKNOWN_ERROR` quando a API não mandou código. |
| `message` | `error.message` da API. Quando o corpo da resposta não é o envelope de erro, é a mensagem genérica do cliente HTTP. |
| `details` | `error.details` da API, quando houver. |
| `retryAfter` | Segundos do header `Retry-After`, quando houver. |

A API responde 401 para chave inválida, revogada ou expirada, e 403 quando a chave não tem permissão para a rota. O corpo desses erros de autenticação não vem no envelope da API: o SDK lança `AuthenticationError` com `code` `UNKNOWN_ERROR`. Teste a classe ou o `statusCode`, não o `code`. Lista de códigos em [Erros](/erros).

## Retentativas e idempotência

O SDK retenta, até `maxRetries` vezes:

* falha de rede (sem resposta);
* 429, respeitando `Retry-After` (até 30s);
* qualquer 5xx.

O intervalo começa em 500ms e dobra a cada tentativa, até 30s. Outros 4xx não são retentados. Para desligar: `maxRetries: 0`.

Só se retenta requisição segura de repetir: `GET`, `PUT`, `DELETE` e `POST` com `Idempotency-Key`. `messages.send`, `messages.sendBatch` e `campaigns.create` sempre mandam esse header, então uma retentativa repete a mesma chave e não duplica o envio. `templates.create` não manda a chave e não é retentado.

`send` aceita um segundo argumento com `idempotencyKey`, enviado no header `Idempotency-Key`. Sem ele, o SDK gera um UUID por chamada (igual em todas as retentativas dela), o que não protege contra reenvio do seu lado. Derive a chave de um id do seu sistema (id do pedido, id da tentativa), não de um valor novo a cada chamada:

```typescript theme={null}
await sdk.messages.send(
  { receiver: 'whatsapp:+5511999999999', templateName: 'confirmacao_pedido', templateVariables: ['João'] },
  { idempotencyKey: `confirmacao-${order.id}` }
);
```

Repetir a mesma chave devolve a mensagem original, sem novo envio. A API não compara o corpo: a mesma chave com destinatário ou variáveis diferentes também devolve a mensagem original, e a chave não expira.

## Webhooks

O SDK exporta guards de tipo para o corpo do webhook:

```typescript theme={null}
import { WebhookUtils } from '@ararahq/sdk';

function handleWebhook(payload: { event: string }) {
  if (WebhookUtils.isMessageStatusEvent(payload)) {
    console.log(payload.data.status, payload.data.receiver); // delivered, read, failed...
    return;
  }
  if (WebhookUtils.isInboundMessageEvent(payload)) {
    console.log(payload.data.from, payload.data.body);
    return;
  }
  if (WebhookUtils.isRevenueRecoveryEvent(payload)) {
    // cart.abandoned, payment.failed, pix.created, boleto.due
    console.log(payload.event, payload.data.phone);
  }
}
```

`WebhookUtils.parseEvent(payload)` devolve o evento se for um desses tipos, ou `null`. Os demais eventos (`conversation.needs_human`, `template.updated` etc.) não têm guard; trate pelo campo `event`.

O SDK **não** valida a assinatura. Valide antes de processar, com o corpo cru da requisição:

```typescript theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';

const TOLERANCE_SECONDS = 300;

export function isValidAraraSignature(rawBody: string, timestamp: string, signature: string, secret: string): boolean {
  const ts = Number(timestamp);
  if (!Number.isInteger(ts) || Math.abs(Date.now() / 1000 - ts) > TOLERANCE_SECONDS) return false;
  const expected = 'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}
// timestamp = header X-Arara-Timestamp, signature = header X-Arara-Signature
```

Detalhes em [Webhooks](/webhooks).

## Recursos disponíveis

| Recurso | Acesso | Métodos |
| - | - | - |
| Messages | `sdk.messages` | `send(payload, options?)` |
| Templates | `sdk.templates` | `list()`, `create(payload)`, `get(id)`, `getStatus(id)`, `delete(id)` |
| Auth | `sdk.auth` | `me()` (chave ADMIN) |
| Opt-outs | `sdk.optOuts` | `list()`, `get(phone)`, `create(request)`, `delete(phone)` |
| Contacts | `sdk.contacts` | `list()`, `importBatch()`, `stats()`, `reactivationCandidates()`, `listTags()`, `get(phone)`, `update(phone, patch)`, `messages(phone)` |
| Conversations | `sdk.conversations` | `list()`, `leadStats()`, `messages(id)`, `reply(request)`, `updateStatus(id, status)`, `windowStatus(phones)` |
| Campaigns | `sdk.campaigns` | `create(payload, options?)`, `list()`, `estimate(templateName, count)`, `get(id)`, `cancel(id)` |
| Wallet | `sdk.wallet` | `transactions()`, `getAutoRecharge()`, `updateAutoRecharge(request)` |
| Numbers | `sdk.numbers` | `list()`, `update(id, request)`, `delete(id)`, `request(request)`, `listRequests()`, `sync(id)`, `warming(id)` |
| Smart links | `sdk.smartLinks` | `create()`, `update()`, `list()`, `stats(id)` |
| REST genérico | `sdk.api` | `get`, `post`, `put`, `patch`, `delete` para endpoints sem recurso tipado; herda autenticação, `baseUrl`, timeout e retentativas |

As leituras de contacts, conversations, wallet, smart links, opt-outs e `auth.me()` exigem chave ADMIN. Com chave só `READ`, lançam `AuthenticationError` (403). `optOuts` recusa telefone fora do formato `+5511999999999` com `RangeError`, antes de chamar a API.

### TypeScript

Tipos exportados na 2.0.0 (exemplos; o pacote exporta também os tipos de contatos, campanhas, carteira, números e smart links):

```typescript theme={null}
import type {
  SDKConfig,
  SendMessageRequest,
  SendMessageOptions,
  MessageResponse,
  Template,
  CreateTemplateRequest,
  TemplateResponse,
  TemplateStatus,
  PaginatedResponse,
  AraraWebhookEvent,
  MessageStatusWebhookEvent,
  RevenueRecoveryWebhookEvent,
  AbacatePayWebhookEvent,
} from '@ararahq/sdk';
```

`InboundMessageWebhookEvent` existe no código mas não é exportado pelo pacote.

## Limitações conhecidas

| Método | Problema |
| - | - |
| Chaves de API, perfil do usuário e webhook da organização | O SDK não tem métodos para isso, e a API recusa esses endpoints com chave de API. Use o [painel](https://app.ararahq.com/dashboard/webhooks). |
| `templates.get/getStatus/delete/analytics` | Precisam do id (UUID). Com o nome do template a API responde 400. |
| `templates.create` | Não manda `Idempotency-Key` e não é retentado. |
| Validação de assinatura de webhook | O SDK não valida. Use a função da seção Webhooks. |

## Próximos passos

<CardGroup cols={2}>
  <Card title="Next.js" icon="js" href="/integracoes/nextjs">
    Server Actions e route handlers.
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Endpoints REST que o SDK chama por baixo.
  </Card>
</CardGroup>

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


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