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.
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:
{ "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.
POST /v1/instances/{instanceId}/official/credentials
{ "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:
GET /v1/instances/{instanceId}/official/credentials
{
"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:
{
"name": "Atendimento Principal"
}
Resposta 201:
{
"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:
[
{
"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_erroraparece quando a instância teve um problema (ex: conflito de número). E limpo automaticamente quando a instância conecta com sucesso. Os camposban_expiryeban_reasonaparecem 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:
{
"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:
{
"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-namefora do tamanho permitido (3-80)404 INSTANCE_NOT_FOUND- instância não pertence a empresa autenticada409 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
200com 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:
{
"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:
{
"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_inboundfora do range 1-10404 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:
-
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.
- Texto: hash do
-
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):cold2 ·warm3 ·engaged4 ·hot5 ·very_hot7. Bloqueio retorna409 CONFLICTcomerror_code: "BLOCKED_TEMPERATURE_LIMIT"(payload incluiremote_jid,score,tier,max_consecutive,outbound_consecutive). - Quando
anti_spam_temperature_enabled=false→ vale o limite fixomax_outbound_without_inbound(1-10, default 2), rolling window 7d. Cada mensagem inbound zera o contador. Bloqueio retorna409 CONFLICTcomerror_code: "BLOCKED_NO_RECIPROCITY"(payload incluiremote_jid,count,threshold).
Em ambos os casos, o contador subjacente é zerado a cada inbound do contato.
- Quando
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:
{ "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.