Códigos de erro
Toda resposta de erro da API segue o mesmo formato:
{
"error": "codigo_estavel",
"message": "Frase em português explicando o problema",
"details": { "campos extras, quando existem": "..." },
"request_id": "req_...",
"docs_url": "https://wevi.chat/docs/api/erros#codigo_estavel"
}
O campo error é um código estável, em snake_case, que você usa para ramificar a lógica no seu código. O message é para humanos e pode mudar. Vale para todos os endpoints, inclusive os nove send-* antigos: neles a frase que a integração já conhecia (connection_id is required) continua igual, só que em message, e o error virou código (missing_connection_id).
Toda resposta traz o header X-Request-Id. Guarde esse valor: com ele a gente acha a chamada exata no log e você mesmo a encontra em Conta → Desenvolvedores → Requisições recentes, com status, tempo de resposta e código do erro dos últimos 7 dias. Se preferir gerar o id do seu lado, mande no header X-Request-Id da requisição e ele é reaproveitado.
Códigos HTTP
| Status | Significado |
|---|---|
| 200 | Sucesso. Mesmo erros de "negócio" (ex: atribuição de carteira falhou mas contato foi criado) podem vir aqui com campo ..._error no payload. |
| 400 | Body inválido, falta campo obrigatório, formato errado. |
| 401 | Autenticação faltando ou inválida. |
| 402 | Plano não permite essa feature (trial expirado, plano não tem WhatsApp, etc). |
| 403 | Autenticado mas sem permissão (ex: contato fez opt-out, fluxo inativo). |
| 404 | Recurso não existe (connection_id errado, etc). |
| 409 | Conflito de estado (ex: contato já está numa run ativa de automação). |
| 422 | Validação semântica falhou (ex: número de telefone fora do formato). |
| 429 | Rate limit excedido. Veja rate limits. |
| 500 | Erro no nosso lado. Tente novamente; se persistir, abra um chamado. |
Erros mais comuns
Em qualquer endpoint
error |
Status | Quando |
|---|---|---|
invalid_json |
400 | O corpo não é JSON válido, ou não é um objeto. |
unknown_fields |
400 | O corpo tem uma chave que o endpoint não conhece. details.unknown_fields lista as chaves rejeitadas e details.allowed_fields as aceitas. Um typo em assigned_user_email deixa de virar contato sem dono em silêncio. |
route_not_found |
404 | O caminho não existe. |
method_not_allowed |
405 | O método não vale para este caminho. |
rate_limit_exceeded |
429 | Veja rate limits. Vem com Retry-After. |
internal_error |
500 | Erro nosso. Guarde o request_id e tente de novo. |
/contacts
error |
Quando |
|---|---|
missing_identifier |
Body não tem nem email nem phone (ou, nas sub-rotas, nenhum de id, email, phone, cpf). |
invalid_fields |
Mandou fields como array, string ou null. |
unknown_fields |
Chaves em fields não estão cadastradas em Campos personalizados, ou chave desconhecida no topo do corpo. details.unknown_fields traz a lista. |
contact_not_found |
O identificador não bate com nenhum contato da organização. |
plan_limit_exceeded |
Org com trial expirado ou plano cancelado. |
Sucesso parcial: se assigned_user_email falhar (email não é membro da org, role inválida), o contato é criado/atualizado mesmo assim e a resposta inclui assignment_error: "..." em vez de assigned_user_id.
/contacts-schema
error |
Quando |
|---|---|
invalid_api_key |
API key inválida ou revogada. |
rate_limit_exceeded |
60 req/min excedido. |
/automation-trigger
error |
Quando |
|---|---|
phone is required |
Body sem phone. |
Invalid token |
Token errado ou fluxo não tem webhook habilitado. |
opted_out |
Contato fez opt-out global; fluxo não dispara. |
Webhook trigger disabled for this flow |
Fluxo não tem trigger webhook ativo. |
Flow is not active |
Fluxo está em draft ou pausado. |
Already in flow |
Contato já está numa run ativa desse fluxo. |
cooldown |
Contato saiu desse fluxo há menos tempo que o cooldown configurado. Resposta inclui retry_at (ISO8601). |
Erros de chave
error |
Quando |
|---|---|
missing_authorization |
O header Authorization não veio. |
invalid_api_key |
A chave não existe ou foi revogada. Uma chave revogada pode continuar válida por até 30 segundos, o tempo do cache de autorização. |
expired_api_key |
A chave passou da data de expiração escolhida na criação. |
ip_not_allowed |
A chamada veio de um endereço fora da lista de IPs configurada na chave. details.your_ip diz de onde ela veio. |
insufficient_scope |
A chave não tem o escopo exigido pelo endpoint. details.required_scope diz qual. Veja autenticação. |
plan_limit_exceeded com feature: "api" |
O plano não inclui escrita e envio pela API. Leitura continua liberada em qualquer plano. |
POST /messages e os send-*
error |
Status | Quando |
|---|---|---|
missing_type |
400 | POST /messages sem type. |
missing_connection_id, missing_to, missing_text, missing_body, missing_template |
400 | Falta campo obrigatório. Nos send-*, o message traz a frase antiga (connection_id is required). |
missing_destination |
400 | Nem to nem conversation_id. |
no_connection |
400 | A organização não tem número conectado. |
connection_not_found |
404 | connection_id não pertence à sua org. |
conversation_not_found |
404 | conversation_id não pertence à sua org. |
not_a_whatsapp_conversation |
400 | A conversa é de outro canal. |
missing_media, incompatible_mime |
400 | Mídia sem url/base64 ou mime_type, ou tipo que não combina. |
invalid_buttons, invalid_sections, invalid_url, invalid_header |
400 | Interativo malformado. message explica o que faltou. |
catalog_not_configured |
403 | product ou product_list sem catalog_id na conexão. |
opted_out |
403 | O contato pediu para sair e a janela de 24 h está fechada. |
idempotency_key_reused |
422 | A mesma Idempotency-Key veio com um corpo diferente. details.first_send_id aponta o envio original. |
plan_limit_exceeded |
402 | Plano sem escrita pela API ou trial expirado. details.feature e details.current_plan. |
Se o Meta rejeitar a mensagem (template não aprovado, fora da janela de 24h pra texto livre, etc), a resposta vem com HTTP 200 mas status: "failed" e error com a mensagem do Meta. Trate ambos os casos.
O /send-conversation-message segue essa mesma regra. Antes ele devolvia 500 para recusa do Meta, o que fazia cliente com retry automático reenviar a mensagem. Hoje recusa do Meta é sempre 200 com status: "failed", nos dois endpoints.
/webhooks
error |
Quando |
|---|---|
invalid_url |
URL não é https, ou aponta para endereço local ou de rede privada. |
unknown_event_type |
Algum tipo em events não existe. A resposta traz details.unknown com a lista. |
invalid_events |
events não é lista, ou veio vazia. |
webhook_not_found |
O id não é desta organização. |
delivery_not_found |
A entrega não pertence a este endpoint. |
admin_only |
A sessão do painel não é de um administrador. |
Idempotência
Todo envio aceita o header Idempotency-Key (ou o campo idempotency_key no corpo). Se você repetir a chamada com a mesma chave e o mesmo corpo, recebe o resultado do envio original sem reenviar. Recomendado para não duplicar mensagens em caso de timeout de rede.
Se a mesma chave vier com um corpo diferente, a resposta é 422 idempotency_key_reused. Antes, a resposta antiga era devolvida em silêncio, e um bug no seu lado que reaproveitasse chaves mandava a mensagem errada com 200.
await fetch("/send-template", {
method: "POST",
headers: { /* ... */ },
body: JSON.stringify({
connection_id: "...",
template_name: "boas_vindas",
to: "+5511999999999",
idempotency_key: "lead-12345-welcome",
}),
});
Próximo
Ferramentas para dev