# Conexão do WhatsApp

> Como o cliente final conecta o número dele pela página do hub, e o recurso de conexão.

Fonte: https://metahub.matratecnologia.com/docs/conexoes

O cliente final conecta o número de WhatsApp Business dele ao seu workspace por uma página hospedada pelo hub (Embedded Signup da Meta). Seu app não fala com a Meta: abre uma sessão de conexão, redireciona o cliente para a `url` devolvida e acompanha o resultado por redirect de volta e por eventos.

## Pré-requisito: URLs de retorno

O `returnUrl` da sessão precisa estar na allowlist do seu app, cadastrada pela equipe Matra no painel. A comparação é por `origin + pathname`, sem query nem fragmento: o hub normaliza a URL enviada e exige que seja igual a uma entrada. Fora da lista: `400` com `code: "RETURN_URL_NOT_ALLOWED"`.

## Abrir uma sessão

`POST /v1/workspaces/:workspaceId/connect-sessions`

```json
{ "kind": "whatsapp", "returnUrl": "https://app.exemplo.com/settings/whatsapp" }
```

Corpo estrito (campo extra é rejeitado). Resposta `201`:

```json
{
  "id": "...",
  "url": "https://.../connect#s=cs_...",
  "expiresAt": "2026-10-22T15:15:00.000Z"
}
```

- A `url` é de uso único e vale 15 minutos (`expiresAt`). Ela carrega um token de acesso: não registre em log nem exiba para quem não é o cliente que vai conectar.
- Repetir o `POST` abre uma sessão nova; as anteriores seguem válidas até expirar ou serem usadas.

| Status | `code` | Quando |
|---|---|---|
| 400 | `RETURN_URL_NOT_ALLOWED` | `returnUrl` fora da allowlist do app |
| 404 | `WORKSPACE_NOT_FOUND` | workspace inexistente ou de outro app |
| 409 | `WORKSPACE_DISABLED` | workspace desativado |
| 503 | `META_WEBHOOK_NOT_READY` | a Meta ainda não está entregando webhooks ao hub; uma conexão feita agora nasceria sem receber eventos. Tente de novo em instantes; se persistir, avise a equipe Matra |

## Retorno do cliente

Ao terminar, cancelar ou falhar, a página redireciona o navegador para o seu `returnUrl` com parâmetros na query:

| Query | Significado |
|---|---|
| `status=connected&connectionId=<id>` | Conexão criada. Ainda está `pending`: a ativação roda em segundo plano |
| `status=cancelled` | O cliente fechou ou cancelou o fluxo da Meta |
| `status=error&code=<code>` | Falha (`META_EXCHANGE_FAILED`, `META_GRANT_INVALID`, `META_ASSET_MISMATCH`, `META_PHONE_AMBIGUOUS`, `COEXISTENCE_INCOMPLETE`, `PHONE_ALREADY_CONNECTED`, `WABA_ALREADY_CONNECTED`, `META_UNAVAILABLE`…) |

O redirect é só uma dica de interface: o navegador do cliente pode fechar antes dele. **A fonte da verdade é o evento `connection.created`** (e `GET /v1/connections`).

`PHONE_ALREADY_CONNECTED` e `WABA_ALREADY_CONNECTED` indicam que o número ou a conta já estão ligados a uma conexão viva em outro workspace; o hub não diz de quem. `META_PHONE_AMBIGUOUS` sai no Coexistence quando a Meta não informa o número e a conta tem zero ou mais de um número do app WhatsApp Business já migrado; `COEXISTENCE_INCOMPLETE`, quando o número do app ainda não terminou a migração para a API. Nos dois casos nada é criado e o cliente pode tentar de novo com uma sessão nova.

Sessão de um workspace desativado, ou de um app desativado pela Matra, deixa de valer na hora, mesmo dentro dos 15 minutos: a página mostra que o link não vale mais e nenhuma conexão é criada.

## Conexões

| Método e rota | O que faz | Respostas |
|---|---|---|
| `GET /v1/connections` | Lista (paginada) as conexões do app. Filtros: `workspaceId`, `status` | 200 |
| `GET /v1/connections/:connectionId` | Busca uma conexão | 200, 404 |
| `DELETE /v1/connections/:connectionId` | Desconecta; repetir é seguro | 200, 404 |
| `GET /v1/connections/:connectionId/usage` | Uso do nível gratuito de mensagens de serviço no mês (ver [Custos](https://metahub.matratecnologia.com/docs/custos.md)) | 200, 404 |
| `GET /v1/connections/:connectionId/consumption?month=YYYY-MM` | Mensagens entregues no mês por categoria e tipo de cobrança, contadas pelo hub e pela Meta (ver [Custos](https://metahub.matratecnologia.com/docs/custos.md)) | 200, 400, 404 |

Resposta de uma conexão (`{ "connection": ... }` no `GET` por id e no `DELETE`; item de `data` na listagem):

```json
{
  "id": "...",
  "kind": "whatsapp",
  "status": "active",
  "errorCode": null,
  "workspaceId": "...",
  "whatsapp": {
    "wabaId": "123456789012345",
    "phoneNumberId": "109876543210987",
    "displayPhoneNumber": "+55 43 99999-0000",
    "verifiedName": "Clínica Exemplo",
    "isOnBizApp": true,
    "qualityRating": "GREEN"
  },
  "sync": [
    { "type": "smb_app_state_sync", "status": "accepted", "progress": null },
    { "type": "history", "status": "accepted", "progress": 40 }
  ],
  "tokenExpiresAt": null,
  "health": {
    "status": "LIMITED",
    "issues": [
      {
        "entityType": "PHONE_NUMBER",
        "canSendMessage": "LIMITED",
        "errors": [],
        "additionalInfo": ["Your display name has not been approved yet. Your message limit will increase after the display name is approved."]
      }
    ],
    "checkedAt": "2026-10-23T16:36:19.000Z"
  },
  "createdAt": "2026-10-22T15:00:00.000Z",
  "updatedAt": "2026-10-22T15:00:00.000Z"
}
```

- `status`: `pending` (criada, ativação em andamento), `active`, `error` (precisa de atenção, ver `errorCode`) ou `disconnected`.
- `errorCode` só vem preenchido em `error`: `offboarded` (a Meta retirou o número do parceiro; volta a `active` se a Meta reconectar), `token_expired` (a credencial venceu; o cliente precisa conectar de novo), `onboarding_incomplete` (a ativação não terminou em 2 h), `activation_failed`, `subscribe_failed:<código>` e `register_failed:<código>`. Trate valores desconhecidos como erro genérico.
- `whatsapp.isOnBizApp` é `true` quando o número continua no app WhatsApp Business do cliente (Coexistence).
- `sync` aparece só em Coexistence: o hub pede à Meta a sincronização do estado do app (`smb_app_state_sync`) e do histórico de 180 dias (`history`). `status`: `requested`, `accepted`, `completed` ou `failed`. `progress` (0 a 100, ou `null` antes do primeiro bloco) é o maior progresso informado pela Meta no histórico; em 100 o `history` vira `completed` e sai um `connection.updated`. O histórico recusado pelo cliente no app vira `failed` (com `connection.updated`). O conteúdo sincronizado chega pelos eventos `whatsapp.history.received` e `whatsapp.contacts.synced`.
- `tokenExpiresAt` é `null` quando a credencial não tem prazo.
- `health` é o `health_status` da Meta para o número, lido na ativação e de hora em hora (`null` antes da primeira leitura). `status` é o `can_send_message` geral: `AVAILABLE`, `LIMITED` (envia com limite menor) ou `BLOCKED` (conversa iniciada pela empresa não sai; resposta dentro da janela pode sair). `issues` lista só as entidades (`PHONE_NUMBER`, `WABA`, `BUSINESS`, `APP`) que não estão `AVAILABLE` ou que têm erro de mensagem, com `errors` (`code`, `description` e `solution` como a Meta devolve, em inglês) e `additionalInfo`; erros da API de chamadas (138xxx) ficam de fora. Exemplos vistos: `141010` (empresa sem verificação, `LIMITED`), `141006` (sem forma de pagamento, `BLOCKED`) e o nome de exibição em análise (`additionalInfo`, `LIMITED`). Quando o estado ou os códigos mudam, sai `connection.updated` com `meta: { "health": "changed" }`; a primeira leitura só avisa quando não está `AVAILABLE`.
- Nenhuma resposta traz token, PIN ou qualquer credencial.

**Reconectar.** Uma nova sessão para o mesmo workspace e o mesmo número reaproveita a mesma conexão (mesmo `id`): ela volta a `pending` e emite `connection.updated`, não `connection.created`. Um número que está `active`, `pending` ou `error` em outro workspace não pode ser conectado: o `409` vai para a página de conexão, não para o seu app. O que o seu app vê é o retorno do navegador com `?status=error&code=PHONE_ALREADY_CONNECTED` (ou `WABA_ALREADY_CONNECTED`), e nenhum `connection.created` é emitido.

**Desconectar.** `DELETE` marca `disconnected`, apaga a credencial no hub e cancela a assinatura de webhooks da conta quando nenhuma outra conexão a usa. O número do cliente **não** é desregistrado na Meta. Responde `200` com a conexão; chamar de novo devolve a conexão atual sem novo evento. Conexões desconectadas há mais de 30 dias são apagadas do hub.
