Pular para o conteúdo principal

Gestão de contatos

Toda mensagem que entra ou sai vira contato na sua base — um CRM leve embutido no gateway. O bZapper captura o contato automaticamente da conversa (nome do WhatsApp, avatar, atividade) e você enriquece com os campos de CRM (e-mail, documento, endereço), organiza com tags e grupos de contato, filtra por qualquer critério e acompanha a timeline de cada pessoa.

Contato ≠ grupo de WhatsApp

Aqui tratamos da base de contatos (pessoas) e dos grupos de contato (segmentos de CRM), que vivem em /contacts e /contact-groups. Não confunda com os grupos de WhatsApp (salas de conversa), que ficam em /groups. São coisas diferentes.

Correlação automática

Você não precisa cadastrar nada para começar: a cada mensagem trocada, o bZapper correlaciona o contato ao projeto e ao número que falou com ele. O contato ganha:

  • instance_id — o último número que conversou com ele (útil para afinidade/sticky).
  • last_message_at e message_count — atividade viva.
  • source — como entrou na base: inbound, outbound, api, import ou widget.
  • name e avatar_url — nome de exibição (push name) e foto, best effort.

O telefone é sempre normalizado (+DDIdigits, sem espaços) e o e-mail vira minúsculo no banco — o mesmo contato é reconhecido sem duplicatas.

Carimbar tags e grupos no envio

Os campos groups[] e tags[] de qualquer envio carimbam o contato destinatário quando a mensagem resolve — sem uma chamada extra ao CRM. Chaves desconhecidas são criadas no dicionário na hora.

curl -X POST https://api.bzapper.com.br/messages/text \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{
"to": "+5511999998888",
"body": "Bem-vindo! 🎉",
"tags": ["lead-quente"],
"groups": ["onboarding"]
}'
groups[] no envio NÃO manda para grupo de WhatsApp

groups[] correlaciona o contato a grupos de contato (CRM). Para enviar a uma sala do WhatsApp, use o JID do grupo no campo to.

Perfil do contato

Cada contato tem campos de CRM em colunas separadas — nada de "notas soltas":

CampoO que é
phonetelefone +DDIdigits (obrigatório na criação)
namenome (push name do WhatsApp ou o que você informar)
emaile-mail (armazenado em minúsculo)
document / document_typedocumento (CPF/CNPJ/passaporte…) e seu tipo
addressendereço em campos separados: street, number, complement, district, city, state, zip, country
tags / groupschaves de tags e grupos de contato correlacionados
statusestado do ciclo de vida (veja abaixo)
instance_idúltimo número que conversou com ele
message_count, last_message_at, created_at, updated_atatividade

Estados (status)

EstadoSignificado
activeativo, recebe normalmente
pending_validationainda não validado
opted_outpediu descadastro (LGPD) — bloqueado para envio
blockedbloqueado manualmente por um admin
unreachableproblema de recebimento (bounce/inferido)

Criar e atualizar

# Criar (phone obrigatório; o resto é opcional)
curl -X POST https://api.bzapper.com.br/contacts \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{
"phone": "+5511999998888",
"name": "Vinicius Berni",
"email": "[email protected]",
"document": "12345678900",
"document_type": "cpf",
"address": { "city": "Porto Alegre", "state": "RS", "country": "BR" }
}'

Resposta 201:

{
"id": "3f2a…",
"phone": "+5511999998888",
"name": "Vinicius Berni",
"email": "[email protected]",
"document": "12345678900",
"document_type": "cpf",
"address": { "city": "Porto Alegre", "state": "RS", "country": "BR" },
"status": "active",
"source": "api",
"message_count": 0,
"tags": [],
"groups": [],
"created_at": "2026-07-01T12:00:00Z",
"updated_at": "2026-07-01T12:00:00Z"
}

Criar com um telefone que já existe devolve 409. A atualização é parcial (PATCH /contacts/{id}) — mande só os campos que mudaram.

Listar com filtros avançados

GET /contacts aceita um leque de filtros combináveis e paginação por offset:

FiltroO que faz
searchnome, telefone, e-mail ou documento
tags + tags_matchchaves de tag; casa any (padrão) ou all
groupschaves de grupo de contato
statusactive · pending_validation · opted_out · blocked · unreachable
city · state · country · zip · documentfiltros do perfil
has_emailsó quem tem (true) ou não tem (false) e-mail
instance_idúltimo número que falou com o contato
last_activity_after / _beforejanela de last_message_at (RFC3339)
created_after / _beforejanela de criação (RFC3339)
sortlast_activity (padrão) · name · created
limit / offsetpaginação (limit padrão 200, máx. 500)
# Leads quentes em POA, com e-mail, ativos, ordenados por atividade
curl -G https://api.bzapper.com.br/contacts \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
--data-urlencode "tags=lead-quente" \
--data-urlencode "city=Porto Alegre" \
--data-urlencode "has_email=true" \
--data-urlencode "status=active"

Resposta:

{ "data": [ { "id": "…", "phone": "+55…", "name": "…", "tags": ["lead-quente"] } ],
"total": 1, "limit": 200, "offset": 0 }

Timeline (histórico)

GET /contacts/{id}/history devolve a linha do tempo unificada — mensagens (entrada/saída) e eventos (opt-out, notas, mudanças de tag/grupo, transições de status) — mais recente primeiro.

{
"data": [
{ "kind": "message", "type": "text", "direction": "inbound",
"status": "received", "actor": "contact", "payload": { "body": "oi" },
"created_at": "2026-07-01T12:03:00Z" },
{ "kind": "event", "type": "tag_added", "actor": "api",
"payload": { "tag": "lead-quente" }, "created_at": "2026-07-01T12:00:00Z" }
]
}

Notas internas

POST /contacts/{id}/notes adiciona uma nota interna (auditoria/CRM) — nunca enviada ao contato; aparece na timeline.

curl -X POST https://api.bzapper.com.br/contacts/$ID/notes \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{ "body": "Cliente pediu retorno na sexta." }'

Tags e grupos de contato

Tags e grupos de contato são dicionários próprios do projeto — cada entrada tem key (slug estável usado na correlação), name, color e a contagem de contatos.

# Criar uma tag
curl -X POST https://api.bzapper.com.br/tags \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{ "key": "lead-quente", "name": "Lead quente", "color": "#22c55e" }'

# Criar um grupo de contato (segmento de CRM — NÃO grupo de WhatsApp)
curl -X POST https://api.bzapper.com.br/contact-groups \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{ "key": "onboarding", "name": "Onboarding" }'

Listar (GET /tags, GET /contact-groups) devolve as entradas com count. Apagar (DELETE /tags/{id}, DELETE /contact-groups/{id}) remove do dicionário e desvincula de todos os contatos.

Correlacionar em lote num contato

# Adiciona/remove tags num contato de uma vez (chaves novas são criadas)
curl -X POST https://api.bzapper.com.br/contacts/$ID/tags \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{ "add": ["lead-quente"], "remove": ["frio"] }'

O mesmo formato ({ "add": [...], "remove": [...] }) vale para POST /contacts/{id}/groups.

Opt-out, supressão e problemas de recebimento

O bloco de privacidade da base tem três ações no contato e uma lista de supressão do projeto:

RotaO que faz
POST /contacts/{id}/optoutmarca opted_out (grava opted_out_at) e adiciona à supressão — LGPD
POST /contacts/{id}/suppressbloqueio manual: status blocked + entrada de supressão
POST /contacts/{id}/optinremove a supressão e reativa (active)
/contacts/{id}/suppress/contacts/{jid}/block

suppress bloqueia o contato no bZapper (não recebe mais envios). Já /contacts/{jid}/block bloqueia o número no WhatsApp (device-level). São camadas distintas.

Lista de supressão do projeto

# Listar números suprimidos
curl https://api.bzapper.com.br/suppressions \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT"

# Adicionar manualmente
curl -X POST https://api.bzapper.com.br/suppressions \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT" \
-H "Content-Type: application/json" \
-d '{ "phone": "+5511999998888", "reason": "manual" }'

# Remover (un-suppress) pelo telefone
curl -X DELETE "https://api.bzapper.com.br/suppressions?phone=%2B5511999998888" \
-H "Authorization: Bearer $BZ_KEY" -H "X-Project-Id: $PROJECT"

Cada entrada guarda phone, reason (ex.: optout, manual, bounce) e source (api, admin, contact, system). Para entender a supressão de dois níveis e o opt-out por palavra-chave, veja Privacidade & LGPD.

Endpoints

MétodoRotaO que faz
GET/contactsLista com filtros avançados + paginação
POST/contactsCria um contato (phone obrigatório)
GET/contacts/{id}Detalha um contato
PATCH/contacts/{id}Atualização parcial dos campos de CRM
DELETE/contacts/{id}Remove o contato
GET/contacts/{id}/historyTimeline (mensagens + eventos)
POST/contacts/{id}/notesAdiciona nota interna
POST/contacts/{id}/tagsCorrelaciona tags em lote (add/remove)
POST/contacts/{id}/groupsCorrelaciona grupos de contato em lote
POST/contacts/{id}/optout · /suppress · /optinOpt-out / bloqueio / reativação
GET · POST/tags · /contact-groupsDicionários (com contagem)
DELETE/tags/{id} · /contact-groups/{id}Remove do dicionário e desvincula
GET · POST · DELETE/suppressionsLista de supressão do projeto
Correlação sem esforço

Na maioria dos casos você nem chama o CRM diretamente: mande tags[]/groups[] no envio e deixe o bZapper carimbar os contatos por você.