/contacts

Endpoint pra criar, atualizar ou ler contatos da sua organização.

  • POST /contacts — cria ou atualiza (upsert).
  • GET /contacts — busca um contato individual por id, email, phone ou cpf.

Auth: Authorization: Bearer wevi_SUA_CHAVE (crie em Conta → Desenvolvedores → API Keys).


POST /contacts

Cria ou atualiza um contato. Se já existe (matched por email ou phone), atualiza. Caso contrário, cria.

Campo Valor
URL https://api.wevi.chat/functions/v1/contacts
Método POST
Auth Authorization: Bearer wevi_SUA_CHAVE
Content-Type application/json
Rate limit 300 req/min por token

Body

Campo Tipo Obrigatório Descrição
email string sim, se phone não vier Email do contato. Lowercase, normalizado.
phone string sim, se email não vier Telefone no formato internacional (ex: +5511999999999).
name string não Nome completo. Vai pra display_name do contato. Sobrescreve sempre que enviado.
fields object não Campos personalizados (chave → valor string). Veja regras abaixo.
tags array de strings não Tags por nome. Se não existir na org, é criada com cor padrão (verde Wevi).
assigned_user_email string não Email do atendente que vira dono da carteira desse contato. Precisa ser membro da org com role admin, editor ou atendente.

Matching: como sabemos se já existe

  1. Procuramos primeiro por email em contact_identifiers.
  2. Se não achar, procuramos por phone.
  3. Se não achar nenhum, criamos contato novo.

Quer matching por CPF ou outro identificador? Use a UI pra cadastrar identificadores e os contatos passam a ser localizáveis também por eles em buscas internas (não via API ainda).

Sobre fields (campos personalizados)

  • Cada chave precisa estar previamente cadastrada na sua org em Conta → Campos personalizados.
  • Valores são salvos como string (mesmo que você mande número ou boolean).
  • Merge: campos existentes do contato não são apagados. Você sobrescreve apenas o que mandar.
  • Se mandar uma chave não cadastrada, a resposta é 400 com unknown_fields: [...].

Pra descobrir quais chaves existem na sua org, use `GET /contacts-schema`.

Sobre tags

  • Array de strings. Vazio ou ausente = não mexe nas tags do contato.
  • Case-insensitive: "VIP" e "vip" são consideradas a mesma tag.
  • Se a tag não existe na org, é criada automaticamente com cor verde.
  • Atribuição não sobrescreve: tags antigas continuam. A API hoje só adiciona.

Exemplos

Mínimo: só email

curl -X POST https://api.wevi.chat/functions/v1/contacts \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@exemplo.com"}'

Completo: nome, telefone, campos, tags e dono

curl -X POST https://api.wevi.chat/functions/v1/contacts \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "joao@exemplo.com",
    "phone": "+5511999999999",
    "name": "João Silva",
    "fields": {
      "cpf": "12345678900",
      "plano_atual": "Pro"
    },
    "tags": ["VIP", "Trial expirando"],
    "assigned_user_email": "maria@suaempresa.com.br"
  }'

Resposta

Sucesso (200)

{
  "ok": true,
  "contact_id": "a1b2c3d4-...",
  "tags_assigned": [
    { "name": "VIP", "id": "uuid-1", "created": false },
    { "name": "Trial expirando", "id": "uuid-2", "created": true }
  ],
  "assigned_user_id": "user-uuid"
}
  • tags_assigned indica quais tags foram aplicadas e se foram criadas agora (created: true) ou já existiam.
  • assigned_user_id aparece se o assigned_user_email foi resolvido com sucesso.

Sucesso com falha parcial de atribuição (200)

Se o assigned_user_email não corresponder a um membro válido, o contato é criado/atualizado normalmente mas vem assignment_error:

{
  "ok": true,
  "contact_id": "a1b2c3d4-...",
  "tags_assigned": [],
  "assignment_error": "assigned_user_email_not_found"
}

Possíveis valores de assignment_error:

  • assigned_user_email_not_found — email não bate com nenhum usuário cadastrado.
  • assigned_user_not_member — usuário existe mas não é membro da sua org.
  • assigned_user_role_not_eligible — usuário é membro mas tem role financeiro (não elegível pra carteira).

Erros (4xx)

Ver lista completa em códigos de erro. Os mais frequentes aqui:

// 400 — falta identificador
{ "error": "At least one of 'email' or 'phone' is required" }

// 400 — chave não cadastrada
{
  "error": "unknown_fields",
  "message": "Algumas chaves em 'fields' não estão cadastradas em Campos personalizados. Cadastre as chaves antes de enviar.",
  "unknown_fields": ["plano_atual"]
}

// 401
{ "error": "Invalid token" }

// 402 — trial expirado
{ "error": "plan_limit_exceeded", "feature": "subscription" }

// 429
{ "error": "Too many requests", "retry_after": 47 }

Padrões de uso

Sincronizar lead novo do CRM

Toda vez que um lead é criado/atualizado no seu CRM, dispare este webhook. O contato vai aparecer na Wevi com tudo já preenchido.

Atribuir vendedor automaticamente

Use assigned_user_email pra que o atendente "dono da carteira" receba transbordos desse contato com prioridade. Veja carteirização pra entender o conceito.

Atualizar status sem duplicar

Como o matching é por email ou phone, mandar o mesmo payload duas vezes só atualiza. Não duplica contato.


GET /contacts

Busca um contato individual com todos os dados (campos nativos, custom, tags, identificadores, dono da carteira e estatísticas de conversa).

Campo Valor
URL https://api.wevi.chat/functions/v1/contacts
Método GET
Auth Authorization: Bearer wevi_SUA_CHAVE
Rate limit 120 req/min por token

Query params

Passe um dos quatro identificadores (ordem de prioridade: idemailphonecpf):

Param Descrição
id UUID do contato. Match exato.
email Email (case-insensitive). Procura primeiro em identifiers, fallback na coluna nativa.
phone Telefone (preferir formato +5511...). Mesmo fallback.
cpf CPF cadastrado como identifier.

Exemplos

# por email
curl "https://api.wevi.chat/functions/v1/contacts?email=cliente@exemplo.com" \
  -H "Authorization: Bearer wevi_SUA_CHAVE"

# por telefone
curl "https://api.wevi.chat/functions/v1/contacts?phone=%2B5511999999999" \
  -H "Authorization: Bearer wevi_SUA_CHAVE"

# por UUID
curl "https://api.wevi.chat/functions/v1/contacts?id=a1b2c3d4-..." \
  -H "Authorization: Bearer wevi_SUA_CHAVE"

Resposta

Sucesso (200)

{
  "id": "a1b2c3d4-...",
  "name": "João Silva",
  "email": "joao@exemplo.com",
  "phone": "+5511999999999",
  "notes": "Cliente VIP, prefere atendimento por WhatsApp",
  "custom_fields": { "cpf": "12345678900", "plano": "Pro" },
  "tags": [
    { "id": "tag-1", "name": "VIP", "color": "#22D650" }
  ],
  "assigned_user": {
    "email": "maria@empresa.com",
    "name": "Maria Silva",
    "role": "atendente"
  },
  "identifiers": [
    { "type": "email", "value": "joao@exemplo.com", "label": null },
    { "type": "phone", "value": "+5511999999999", "label": null },
    { "type": "cpf",   "value": "12345678900",     "label": null }
  ],
  "stats": {
    "conversation_count": 12,
    "first_seen_at": "2026-01-15T10:23:00Z",
    "last_seen_at":  "2026-05-22T14:30:00Z",
    "channels": ["whatsapp", "web"]
  },
  "assigned_at": "2026-03-10T...",
  "assignment_source": "manual",
  "created_at": "2026-01-15T...",
  "updated_at": "2026-05-22T..."
}

Erros

Status error
400 Provide one of: id, email, phone, cpf
401 Missing Authorization header ou Invalid token
404 Contact not found
429 Too many requests

Padrão de uso

Use após criar/atualizar via POST pra confirmar o estado final, ou pra puxar dados frescos quando seu CRM precisa exibir o que está na Wevi (last_seen, número de conversas, dono da carteira).


Operações pontuais

Pra modificar um aspecto específico de um contato sem mandar um upsert completo, use os sub-endpoints abaixo. Todos aceitam um identificador no body (id, email, phone ou cpf) e respondem 404 se o contato não existir.

Auth: Authorization: Bearer wevi_SUA_CHAVE em todos.

POST /contacts/tag — adicionar tag

Anexa uma tag ao contato. Se a tag não existir na org, é criada com cor verde padrão.

Body:

{ "email": "joao@x.com", "tag": "VIP" }
curl -X POST https://api.wevi.chat/functions/v1/contacts/tag \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com","tag":"VIP"}'

Resposta 200:

{
  "ok": true,
  "contact_id": "uuid",
  "tag": { "name": "VIP", "id": "tag-uuid", "created": false }
}

created: true se a tag foi criada agora; false se já existia.

DELETE /contacts/tag — remover tag

Remove a atribuição de uma tag. Não apaga a tag da org, só desassocia do contato. Idempotente: se o contato não tinha a tag, responde removed: false sem erro.

Body:

{ "email": "joao@x.com", "tag": "VIP" }
curl -X DELETE https://api.wevi.chat/functions/v1/contacts/tag \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com","tag":"VIP"}'

Resposta 200:

{ "ok": true, "contact_id": "uuid", "removed": true, "tag": { "id": "tag-uuid", "name": "VIP" } }

Ou se a tag não existia na org:

{ "ok": true, "contact_id": "uuid", "removed": false, "reason": "tag_not_found" }

POST /contacts/assign — atribuir dono

Define o atendente "dono da carteira" do contato. O email precisa pertencer a um membro da org com role admin, editor ou atendente.

Body:

{ "email": "joao@x.com", "assigned_user_email": "maria@empresa.com" }
curl -X POST https://api.wevi.chat/functions/v1/contacts/assign \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com","assigned_user_email":"maria@empresa.com"}'

Resposta 200:

{ "ok": true, "contact_id": "uuid", "assigned_user_id": "user-uuid" }

422 (atendente inválido): assigned_user_email_not_found, assigned_user_not_member ou assigned_user_role_not_eligible.

DELETE /contacts/assign — desatribuir dono

Limpa o dono da carteira. Não exige nenhum campo além do identificador do contato.

curl -X DELETE https://api.wevi.chat/functions/v1/contacts/assign \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com"}'

Resposta 200:

{ "ok": true, "contact_id": "uuid", "unassigned": true }

POST /contacts/note — setar notas

Sobrescreve as notas internas do contato. Mande null ou string vazia pra limpar.

Body:

{ "email": "joao@x.com", "note": "Cliente VIP, prefere atendimento por WhatsApp" }
curl -X POST https://api.wevi.chat/functions/v1/contacts/note \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com","note":"Cliente VIP"}'

Resposta 200:

{ "ok": true, "contact_id": "uuid", "note": "Cliente VIP" }

POST /contacts/field — setar/limpar custom field

Atualiza um único campo personalizado. A key precisa estar cadastrada em Conta → Campos personalizados (descubra via `GET /contacts-schema`). Mande value: null pra apagar a chave.

Body:

{ "email": "joao@x.com", "key": "plano", "value": "Pro" }
# setar
curl -X POST https://api.wevi.chat/functions/v1/contacts/field \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com","key":"plano","value":"Pro"}'

# limpar
curl -X POST https://api.wevi.chat/functions/v1/contacts/field \
  -H "Authorization: Bearer wevi_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"email":"joao@x.com","key":"plano","value":null}'

Resposta 200:

{ "ok": true, "contact_id": "uuid", "key": "plano", "value": "Pro" }

Erros:

  • 400 unknown_field se a chave não está cadastrada.
  • 400 'value' is required (use null to clear) se o body não tem campo value.

Diferença pro POST /contacts em batch

O POST /contacts (upsert) faz tudo de uma vez: cria contato, anexa tags, atribui dono, mescla custom_fields. Use quando você tem o pacote completo de dados.

Os sub-endpoints (/contacts/tag, /contacts/assign, etc) são pra operações individuais sobre contato existente:

  • Webhook do seu CRM disparou "lead foi qualificado" → POST /contacts/tag com "qualificado".
  • Vendedor saiu de férias → POST /contacts/assign com o substituto.
  • Cliente cancelou plano → POST /contacts/field com value: null em plano.

Mais granular, sem precisar buscar o contato primeiro pra montar um payload completo.

Próximo

/contacts-schema