> ## 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.

# Migrar do Twilio

> Roteiro para sair do Twilio WhatsApp para a Arara: conexão do número, recriação de templates, diff de código de envio, webhook de status e inbound, mapeamento de campos.

Esta página é o roteiro técnico para trocar o Twilio WhatsApp pela Arara: o que muda no número, nos templates e no código.

<Note>
  **Prazo típico**: depende da verificação da sua empresa na Meta e da aprovação dos templates, que não são controladas pela Arara. Reserve alguns dias úteis para número e templates, e rode Twilio e Arara em paralelo até validar.
</Note>

## O que muda

| | Twilio WhatsApp | Arara |
| - | - | - |
| Identificação do template no envio | `contentSid` (`HX...`) | `templateName` (nome do template) |
| Variáveis | `contentVariables` (JSON string) | `templateVariables` (array posicional) |
| Número de origem | `from` obrigatório | `sender` opcional; sem ele, a Arara resolve o número da organização |
| Webhooks | URL por mensagem/serviço, campos no form body | Um webhook por organização, envelope JSON `{ event, data }`, assinado |
| Resposta ao inbound | TwiML opcional | Chamada à API (`POST /v1/messages`) |
| Cobrança | USD | BRL, via carteira da organização |

Recursos da Arara sem equivalente direto no Twilio: [Brain](/brain), [Smart Links](/smart-links), [recuperação de receita](/recovery), [CLI](/cli) e [MCP server](/mcp-server). Preços em [Preços](/concepts/pricing).

## Número e templates

* **Número**: você conecta o número à Arara pelo dashboard ([https://app.ararahq.com](https://app.ararahq.com)), em Números, pelo fluxo de cadastro da Meta. Se o número está hoje em uma conta WhatsApp Business do Twilio, a liberação e a transferência seguem as regras da Meta para troca de provedor.
* **Templates**: templates pertencem à conta WhatsApp Business (WABA). Recrie cada template na Arara (dashboard ou `POST /v1/templates`) e aguarde a aprovação da Meta antes do cutover.
* **Histórico**: a Arara não importa mensagens antigas do Twilio. Exporte antes de encerrar a conta.

Criar um template pela API:

```bash theme={null}
curl -X POST https://api.ararahq.com/v1/templates \
  -H "Authorization: Bearer $ARARA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "confirmacao_pedido",
    "category": "UTILITY",
    "language": "pt_BR",
    "body": "Oi {{1}}, seu pedido {{2}} foi confirmado.",
    "samples": { "1": "João", "2": "ARA-123" }
  }'
```

A resposta (`201`) traz o `id` (UUID) do template. Consultas de status (`GET /v1/templates/{id}/status`) usam esse id; o envio usa o **nome**.

## Diff de código

### Envio de template

<CodeGroup>
  ```typescript Twilio (antes) theme={null}
  import twilio from 'twilio';
  const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

  await client.messages.create({
    from: 'whatsapp:+14155238886',
    to: 'whatsapp:+5511999999999',
    contentSid: 'HXabc123...',
    contentVariables: JSON.stringify({ '1': 'João', '2': 'ARA-123' }),
  });
  ```

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

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

  await sdk.messages.send(
    {
      receiver: '+5511999999999',
      templateName: 'confirmacao_pedido',
      templateVariables: ['João', 'ARA-123'],
    },
    { idempotencyKey: 'pedido-ARA-123-confirmacao' },
  );
  ```
</CodeGroup>

Diferenças:

* **`contentSid` → `templateName`**: o nome do template, não um SID.
* **`contentVariables` → `templateVariables`**: array posicional. Se o template tem header de mídia, a URL da mídia vai na primeira posição.
* **Sem `from`**: `sender` é opcional.
* **Idempotência**: o header `Idempotency-Key` evita envio duplicado em retry.

### Webhook de status

<CodeGroup>
  ```typescript Twilio (antes) theme={null}
  app.post('/twilio/status', (req, res) => {
    const { MessageSid, MessageStatus } = req.body;
    res.send('OK');
  });
  ```

  ```typescript Arara (depois) theme={null}
  import express from 'express';
  import { createHmac, timingSafeEqual } from 'node:crypto';

  const TOLERANCE_SECONDS = 300;

  function isValidSignature(rawBody: string, timestamp: string, signature: string, secret: string) {
    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);
  }

  app.post('/arara/webhook', express.text({ type: 'application/json' }), (req, res) => {
    const rawBody = req.body as string;
    const ok = isValidSignature(
      rawBody,
      req.get('X-Arara-Timestamp') ?? '',
      req.get('X-Arara-Signature') ?? '',
      process.env.ARARA_WEBHOOK_SECRET!,
    );
    if (!ok) return res.status(401).end();

    const { event, data } = JSON.parse(rawBody);
    if (event === 'message.status_updated') {
      // data.id (ara_msg_...), data.status: queued | processing | sent | delivered | read | failed | canceled
    }
    res.status(204).end();
  });
  ```
</CodeGroup>

Diferenças:

* **Assinatura**: `X-Arara-Signature` = `sha256=` + HMAC-SHA256 de `{X-Arara-Timestamp}.{corpo cru}` com o secret da organização (`whsec_...`). Valide com o corpo cru, não com o JSON já parseado.
* **Envelope único**: `{ event, apiVersion, data, timestamp, organizationId }`.
* **Um webhook para todos os eventos**, configurado no dashboard (URL e secret).

### Mensagem recebida (cliente respondeu)

<CodeGroup>
  ```typescript Twilio (antes) theme={null}
  app.post('/twilio/inbound', (req, res) => {
    const { From, Body, MessageSid } = req.body;
    const phone = From.replace('whatsapp:', '');
    res.set('Content-Type', 'text/xml');
    res.send('<Response></Response>');
  });
  ```

  ```typescript Arara (depois) theme={null}
  // Mesma rota do webhook acima
  if (event === 'message.received') {
    const phone = data.from; // só dígitos, ex.: 5511999999999
    const text = data.body;
    // Resposta via API, dentro da janela de 24h
    await sdk.messages.send({ receiver: `+${phone}`, body: 'Obrigado!' });
  }
  ```
</CodeGroup>

Diferenças:

* **Sem TwiML**: a resposta é uma chamada à API.
* **Mensagem sem template só dentro da janela de 24h** aberta pela mensagem do cliente. Fora dela, use template. Veja [Janela de 24h](/concepts/24h-window).
* **`data.from` vem só com dígitos**, sem `whatsapp:` e sem `+`.

## Mapeamento de campos

| Twilio | Arara | Notas |
| - | - | - |
| `MessageSid` | `id` | Resposta do envio e `data.id` no webhook. Formato `ara_msg_...`. |
| `MessageStatus` | `data.status` | No webhook, minúsculo: `queued`, `processing`, `sent`, `delivered`, `read`, `failed`, `canceled`. |
| `From` (inbound) | `data.from` | Só dígitos. |
| `To` (envio) | `receiver` | `+55...` |
| `Body` | `body` / `data.body` | |
| `contentSid` | `templateName` | |
| `contentVariables` | `templateVariables` | Array posicional. |
| `MediaUrl0` (inbound) | `data.media_url` | Com `data.type = "media"` e `data.media_content_type`. |
| `ProfileName` | `data.sender_name` | |
| `ErrorCode` | `data.errorDetails` (webhook) / `reason` (API) | Motivo da falha. |

## Plano de migração

### Preparação

1. Liste templates e números em uso no Twilio.
2. Crie a organização na Arara em [https://app.ararahq.com](https://app.ararahq.com) e gere uma chave de API.
3. Recrie os templates (`POST /v1/templates`) e acompanhe a aprovação.
4. Conecte o número em Números, no dashboard.

### Cutover

5. Configure o webhook da organização (URL + secret) no dashboard.
6. Atualize as variáveis de ambiente: `ARARA_API_KEY` e `ARARA_WEBHOOK_SECRET`.
7. Faça deploy do código adaptado.
8. Mantenha a conta Twilio ativa até validar entrega, inbound e webhooks na Arara.

### Depois do cutover

9. Acompanhe no dashboard: entregas, falhas e qualidade do número.
10. Encerre o Twilio quando a operação estiver estável.

## Pegadinhas

### Nome do template

Use nomes que façam sentido para o time (`pedido_confirmado`). O envio sempre usa esse nome; o id (UUID) serve para consultar ou apagar o template.

### Botões

`POST /v1/templates` aceita botões `QUICK_REPLY`, `URL`, `PHONE_NUMBER`, `COPY_CODE` e `SMART_LINK` (link rastreável da Arara).

### Mídia com URL temporária

Se as mídias estão em URLs que expiram, hospede em um endereço estável ou suba pela Arara (`POST /v1/media/upload`, multipart com o campo `file`), que devolve `url`.

### Twilio Conversations e Studio

A Arara é focada em WhatsApp: outros canais do Conversations não migram. Fluxos do Studio precisam ser recriados no seu backend, em [automações](/automacoes-webhook) ou no [Brain](/brain).

## Próximos passos

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Primeiro envio.
  </Card>

  <Card title="SDK Node" icon="node" href="/sdks/node">
    Métodos disponíveis na versão publicada.
  </Card>
</CardGroup>


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