Conexões WhatsApp

6. Conexões WhatsApp#

Uma conexão (instância) representa um número WhatsApp Business vinculado à sua conta. No canal oficial, você conecta o número da sua WhatsApp Business Account (WABA) — pelo fluxo guiado Embedded Signup da Meta ou informando as credenciais diretamente. Depois de conectada, a conexão envia e recebe mensagens, e cada evento chega por Webhooks e SSE.

O channel da conexão é whatsapp_official. O provedor interno não é exposto — você trabalha sempre com a mesma API REST.

Conectar o número oficial (Embedded Signup)#

O caminho recomendado. O cliente autoriza a Catcher na tela oficial da Meta e o número é vinculado sem sair do fluxo.

text
GET  /v1/meta/embedded-signup/config
POST /v1/instances/{instanceId}/official/embedded-signup

GET .../config retorna o bootstrap público (app_id, config_id, graph_version, enabled) para iniciar o FB.login(). Após o retorno, envie o code + os waba_id / phone_number_id capturados:

json
{ "code": "<authorization_code>", "waba_id": "...", "phone_number_id": "..." }

A Catcher troca o code por um token permanente no servidor, inscreve a WABA no webhook e conecta. Retorna o estado mascarado da credencial (o token nunca é exposto de volta).

Conectar informando credenciais#

Alternativa direta: informe o phone_number_id, o waba_id e um token permanente de System User da WABA.

text
POST /v1/instances/{instanceId}/official/credentials
json
{ "waba_id": "...", "phone_number_id": "...", "access_token": "<token permanente>" }

A Catcher valida o token na Graph API, inscreve a WABA no webhook, cifra o token (AES-GCM) e conecta. O estado é consultável por:

text
GET /v1/instances/{instanceId}/official/credentials
json
{
  "configured": true,
  "waba_id": "...",
  "phone_number_id": "...",
  "token_hint": "…ABCD",
  "display_phone_number": "+55 41 6349-0888",
  "verified_name": "Catcher",
  "quality_rating": "GREEN",
  "status": "CONNECTED",
  "reauth_required": false
}

Quando o token expira ou é revogado, reauth_required fica true e a conexão emite o evento official.reauth_required — reconecte pelo Embedded Signup ou informe um token novo.

POST/v1/instances#

Cria uma nova conexão (o vínculo do número é feito em seguida pelos endpoints oficiais acima).

Auth: Owner, Admin

Request:

json
{
  "name": "Atendimento Principal"
}

Resposta 201:

json
{
  "id": "84c2e480-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "name": "Atendimento Principal",
  "status": "DISCONNECTED",
  "phone": "",
  "created_at": "2026-03-07T10:00:00Z",
  "updated_at": "2026-03-07T10:00:00Z"
}

GET/v1/instances#

Lista todas as instâncias da empresa.

Auth: Owner, Admin

Resposta 200:

json
[
  {
    "id": "84c2e480-...",
    "name": "Atendimento Principal",
    "status": "CONNECTED",
    "phone": "554137984905",
    "connected_at": "2026-03-07T10:05:00Z",
    "last_seen": "2026-03-07T12:00:00Z",
    "profile_name": "Empresa LTDA",
    "profile_pic_url": "https://...",
    "last_error": "",
    "ban_expiry": null,
    "ban_reason": "",
    "created_at": "2026-03-07T10:00:00Z",
    "updated_at": "2026-03-07T12:00:00Z",
    "uptime": "1h 55m 0s"
  }
]

O campo last_error aparece quando a instância teve um problema (ex: conflito de número). E limpo automaticamente quando a instância conecta com sucesso. Os campos ban_expiry e ban_reason aparecem quando a instância recebeu ban temporario do WhatsApp.


GET/v1/instances/{instanceId}#

Retorna detalhes de uma instância.

Auth: Owner, Admin

Resposta 200: Objeto de instância (mesmo formato da listagem).

Exemplo com erro de conflito:

json
{
  "id": "aaa-bbb-...",
  "name": "Duplicada",
  "status": "DISCONNECTED",
  "last_error": "phone 554192464230 already connected on instance 84c2e480-...",
  "created_at": "2026-03-08T01:00:00Z",
  "updated_at": "2026-03-08T01:00:05Z"
}

Erros:

  • 404 - Instância não encontrada

PATCH/v1/instances/{instanceId}#

Renomeia a instância (label interno BiaZap). O nome não afeta o perfil WhatsApp; ele e usado apenas no Console e nas integrações que listam instâncias.

Auth: Owner, Admin

Request:

json
{
  "name": "Atendimento SP"
}
Campo Tipo Obrigatório Descricao
name string sim 3-80 caracteres após trim. Único por empresa.

Resposta 200: Objeto de instância com o novo name.

Erros:

  • 400 BAD_REQUEST - name fora do tamanho permitido (3-80)
  • 404 INSTANCE_NOT_FOUND - instância não pertence a empresa autenticada
  • 409 DUPLICATE_INSTANCE_NAME - já existe outra instância com esse nome na mesma empresa

Notas:

  • Renomear sempre o mesmo valor (sem alteração real) e idempotente: retorna 200 com a instância atual sem tocar o DB.
  • A operação não reinicia a conexão — e uma alteração puramente de nome no DB do tenant.

GET/v1/instances/{instanceId}/settings#

Retorna configurações operacionais da instância.

Auth: Owner, Admin

Resposta 200:

json
{
  "instance_id": "84c2e480-7c70-4c43-9bb3-f8e5f9ef2e53",
  "humanize_enabled": true,
  "anti_spam_guard_enabled": true,
  "anti_spam_temperature_enabled": true,
  "max_outbound_without_inbound": 2
}
Campo Tipo Descricao
instance_id string ID da instância
humanize_enabled bool Controla a humanizacao automática de mensagens de texto enviadas pela API. Default true.
anti_spam_guard_enabled bool Ativa o guard anti-spam (conteúdo duplicado em 24h + limite consecutivo sem resposta). Default true.
anti_spam_temperature_enabled bool Toggle da curva de temperatura por contato. Quando true (default), substitui o limite fixo max_outbound_without_inbound por uma curva derivada do score por (instance, remote_jid): cold 2 / warm 3 / engaged 4 / hot 5 / very_hot 7.
max_outbound_without_inbound int Limite fixo de envios consecutivos sem resposta (1-10, default 2). Aplicado apenas quando anti_spam_temperature_enabled=false. Ignorado quando a temperatura está ativa.

Erros:

  • 404 INSTANCE_NOT_FOUND - instância não pertence a empresa autenticada

PATCH/v1/instances/{instanceId}/settings#

Atualiza configurações operacionais da instância. Aceita PATCH parcial — qualquer combinação dos campos abaixo.

Auth: Owner, Admin

Request:

json
{
  "humanize_enabled": false,
  "anti_spam_guard_enabled": true,
  "anti_spam_temperature_enabled": true,
  "max_outbound_without_inbound": 3
}
Campo Tipo Obrigatório Descricao
humanize_enabled bool não Ativa/desativa composing + delay aleatorio antes de SendText
anti_spam_guard_enabled bool não Ativa/desativa o guard anti-spam (regras abaixo). Quando false, todas as regras são puladas.
anti_spam_temperature_enabled bool não Quando true (default), a curva de temperatura por contato substitui max_outbound_without_inbound (curva: cold 2 / warm 3 / engaged 4 / hot 5 / very_hot 7). Quando false, vale o limite fixo. O hash-dedup de 24h continua ativo em ambos os casos.
max_outbound_without_inbound int não 1-10. Limite fixo aplicado apenas quando anti_spam_temperature_enabled=false. Ignorado quando a temperatura está ativa.

Ao menos um dos campos e obrigatório. Os campos omitidos preservam o valor atual.

Resposta 200: Mesmo payload do GET /v1/instances/{instanceId}/settings após a atualização.

Erros:

  • 400 MISSING_FIELD - corpo sem nenhum campo aceito (humanize_enabled, anti_spam_guard_enabled, anti_spam_temperature_enabled, max_outbound_without_inbound)
  • 400 BAD_REQUEST - max_outbound_without_inbound fora do range 1-10
  • 404 INSTANCE_NOT_FOUND - instância não pertence a empresa autenticada

Anti-spam guard#

Quando anti_spam_guard_enabled=true (default), todo envio outbound passa por duas regras antes de ser enfileirado:

  1. Conteúdo duplicado (rolling 24h, escopo (instance_id, remote_jid)):

    • Texto: hash do text (trim).
    • Mídia: hash de media_url|media_id + caption + file_name.
    • Localização: hash de (lat, lng, name, address).
    • Contato: hash de (contact_name, contact_phone).
    • Template: hash de body + footer.
    • Reaction fica fora dessa regra (opera em mensagem existente).
    • Reset por inbound: quando o contato responde (qualquer mensagem inbound), TODOS os hashes acumulados nas últimas 24h pra esse (instance_id, remote_jid) são descartados. Permite que respostas curtas naturais ("sim", "ok", "obrigado") se repitam ao longo do dia sem disparar bloqueio. O padrão anti-blast original (mesmo template enviado N vezes em sequência sem nenhuma resposta) continua sendo bloqueado normalmente porque, por definição, não há inbound nele.
  2. Reciprocidade — limite consecutivo sem resposta inbound. A política depende de anti_spam_temperature_enabled:

    • Quando anti_spam_temperature_enabled=true (default) → a curva de temperatura por contato substitui o limite fixo. max_outbound_without_inbound é ignorado. O limite efetivo passa a ser derivado do score por (instance_id, remote_jid): cold 2 · warm 3 · engaged 4 · hot 5 · very_hot 7. Bloqueio retorna 409 CONFLICT com error_code: "BLOCKED_TEMPERATURE_LIMIT" (payload inclui remote_jid, score, tier, max_consecutive, outbound_consecutive).
    • Quando anti_spam_temperature_enabled=false → vale o limite fixo max_outbound_without_inbound (1-10, default 2), rolling window 7d. Cada mensagem inbound zera o contador. Bloqueio retorna 409 CONFLICT com error_code: "BLOCKED_NO_RECIPROCITY" (payload inclui remote_jid, count, threshold).

    Em ambos os casos, o contador subjacente é zerado a cada inbound do contato.

Resumo da precedência:

anti_spam_guard_enabled anti_spam_temperature_enabled Conteúdo duplicado Reciprocidade — limite consecutivo
false qualquer desativado desativado
true false ativo (hash 24h) ativo — limite fixo max_outbound_without_inbound
true true ativo (hash 24h) ativo — curva por score (temperatura), limite fixo ignorado

Bypass: envie o header X-Force-Send: true na requisição. O guard registra audit log com audit_action=anti_spam.force_send por regra ignorada. Tenant admin/owner deve usar com critério.


POST/v1/instances/{instanceId}/connect#

Ativa a conexão da instância com a WhatsApp Business Platform usando as credenciais oficiais já vinculadas (ver Embedded Signup / credenciais acima).

Auth: Owner, Admin

Resposta 200: Objeto de instância atualizado.


POST/v1/instances/{instanceId}/disconnect#

Desconecta a instância do WhatsApp (mantem a sessão para reconexao).

Auth: Owner, Admin

Resposta 200: Objeto de instância atualizado.


POST/v1/instances/{instanceId}/restart#

Reinicia a conexão da instância.

Auth: Owner, Admin

Resposta 200: Objeto de instância atualizado.


DELETE/v1/instances/{instanceId}#

Remove a instância permanentemente.

Auth: Owner, Admin

Resposta 204: Sem corpo.


Chamadas de voz (Calling API)#

No canal oficial, o número WhatsApp Business pode receber chamadas de voz pela Calling API da Meta. Uma chamada recebida chega como o evento call.received (state ringing) e o encerramento como call.ended. O SDP offer nunca é exposto — só o sinal.

POST /v1/instances/{instanceId}/calling#

Liga ou desliga a recepção de chamadas de voz no número oficial.

Auth: Owner, Admin

Request:

json
{ "enabled": true }
Campo Tipo Obrigatório Descrição
enabled bool sim true habilita chamadas de voz no número; false desabilita

Resposta 200: estado atualizado da instância.

POST /v1/instances/{instanceId}/calls/{callID}/reject#

Rejeita uma chamada de voz que está tocando (ringing). callID é o call_id recebido no evento call.received.

Auth: Todos autenticados

Resposta 200: confirmação da rejeição.

POST /v1/instances/{instanceId}/calls/{callID}/hangup#

Encerra uma chamada de voz em andamento. callID é o call_id da chamada.

Auth: Todos autenticados

Resposta 200: confirmação do encerramento.