/contacts
Endpoint pra criar, atualizar ou ler contatos da sua organização.
POST /contacts— cria ou atualiza (upsert).GET /contacts— busca um contato individual porid,email,phoneoucpf.
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
- Procuramos primeiro por
emailemcontact_identifiers. - Se não achar, procuramos por
phone. - 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"}'
await fetch("https://api.wevi.chat/functions/v1/contacts", {
method: "POST",
headers: {
"Authorization": "Bearer wevi_SUA_CHAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({ 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"
}'
await fetch("https://api.wevi.chat/functions/v1/contacts", {
method: "POST",
headers: {
"Authorization": "Bearer wevi_SUA_CHAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
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_assignedindica quais tags foram aplicadas e se foram criadas agora (created: true) ou já existiam.assigned_user_idaparece se oassigned_user_emailfoi 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 rolefinanceiro(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: id → email → phone → cpf):
| 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"
const params = new URLSearchParams({ email: "cliente@exemplo.com" });
const res = await fetch(`https://api.wevi.chat/functions/v1/contacts?${params}`, {
headers: { "Authorization": "Bearer wevi_SUA_CHAVE" },
});
const contact = await res.json();
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"}'
await fetch("https://api.wevi.chat/functions/v1/contacts/tag", {
method: "POST",
headers: { "Authorization": "Bearer wevi_SUA_CHAVE", "Content-Type": "application/json" },
body: JSON.stringify({ 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"}'
await fetch("https://api.wevi.chat/functions/v1/contacts/tag", {
method: "DELETE",
headers: { "Authorization": "Bearer wevi_SUA_CHAVE", "Content-Type": "application/json" },
body: JSON.stringify({ 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"}'
await fetch("https://api.wevi.chat/functions/v1/contacts/assign", {
method: "POST",
headers: { "Authorization": "Bearer wevi_SUA_CHAVE", "Content-Type": "application/json" },
body: JSON.stringify({ 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:
400unknown_fieldse a chave não está cadastrada.400'value' is required (use null to clear)se o body não tem campovalue.
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/tagcom"qualificado". - Vendedor saiu de férias →
POST /contacts/assigncom o substituto. - Cliente cancelou plano →
POST /contacts/fieldcomvalue: nullemplano.
Mais granular, sem precisar buscar o contato primeiro pra montar um payload completo.
Próximo
/contacts-schema