Documentação para desenvolvedores
Public API v1
Receba uma cópia das conversas via webhook e manipule conversas, contatos, tags e setores pela API REST.
Visão geral
Webhook de saída
A Névoa faz POST no seu endpoint com mensagens e eventos de ciclo de vida, assinados com HMAC.
API REST
Endpoints em /api/public/v1 autenticados com Authorization: Bearer <apiToken>.
Escopo por número
Vincule integrações META_WHATSAPP em linkedIntegrationIds. Todo payload de conversa inclui o objeto channel.
Publicações Meta
Crie, agende, reagende, pause e publique posts no Facebook e Instagram em /social, acompanhando cada etapa pelos webhooks social_post.*. Exige vincular integrações Meta (linkedMetaIntegrationIds) — sem vínculo, as rotas respondem 403.
Quickstart
- No painel Integrações, crie uma integração PUBLIC_API.
- Gere e copie o apiToken e o webhookSecret.
- Informe o webhookUrl (HTTPS) e marque os eventos desejados.
- Selecione os números WhatsApp vinculados — ou deixe vazio para todos.
- Para publicar posts, marque as integrações Meta em "Publicações Meta" (e, se quiser, contas específicas).
- Use o Bearer token nas chamadas REST (ex.: POST /messages com phone) e verifique o HMAC nos webhooks.
Base URL
https://api.manager.nevoa.co/api/public/v1Versão atual: v1. Mudanças incompatíveis geram nova versão (v2). A v1 permanece estável. O envelope do webhook inclui apiVersion: "v1".
Autenticação
Todas as rotas em /api/public/v1/** exigem o token da integração PUBLIC_API ACTIVE.
Header
Authorization: Bearer <apiToken>Exemplo
curl -s \
-H "Authorization: Bearer YOUR_API_TOKEN" \
"https://api.manager.nevoa.co/api/public/v1/channels"Rotação de token
- Gere um novo token no painel da integração.
- Atualize o cliente para o novo token.
- Salve a integração — o token antigo deixa de funcionar imediatamente.
- O webhookSecret (HMAC) é independente e pode ser rotacionado da mesma forma.
| Status | Quando |
|---|---|
| 401 | Header ausente, formato inválido, token inexistente ou integração INACTIVE |
| 403 | Rotas /social sem integração Meta vinculada (linkedMetaIntegrationIds) — publicação é opt-in |
| 404 | Recurso fora do escopo linkedIntegrationIds ou do escopo Meta (não vaza existência) |
| 409 | Conflito de negócio (ex.: TEMPLATE_REQUIRED ao enviar free-form fora da janela 24h) |
| 429 | Rate limit da Public API excedido (padrão 120 req/min por integração) |
Canais e escopo
config.linkedIntegrationIds lista as integrações META_WHATSAPP vinculadas. Lista vazia ou ausente = todos os números do tenant (inclusive futuros).
Objeto channel
JSON
{
"integrationId": "wa-uuid",
"name": "WhatsApp Comercial",
"provider": "META_WHATSAPP",
"phoneNumberId": "123",
"displayPhoneNumber": "+5511999999999"
}- Em listagens, use o filtro channelIntegrationId para restringir a um número (deve pertencer ao escopo).
- Rotas com :id de conversa/contato fora do vínculo retornam 404 (não vazam existência).
- Todo payload de mensagem/conversa (API e webhook) inclui o objeto channel.
Canais
Ações — clique para expandir
Endpoints REST
Conversas
Ações — clique para expandir
Mensagens / Templates
Envie free-form por telefone/contato (última conversa) ou inicie com template aprovado.
Ações — clique para expandir
Contatos
Ações — clique para expandir
Catálogos
Ações — clique para expandir
Webhook deliveries
Ações — clique para expandir
Webhooks
A Névoa entrega eventos no seu webhookUrl com envelope versionado e assinatura HMAC.
Envelope e headers
Envelope
{
"id": "uuid-da-entrega",
"event": "message.received",
"apiVersion": "v1",
"occurredAt": "2026-07-19T12:00:00.000Z",
"data": {}
}Content-Type— application/jsonX-Nevoa-Event— Tipo do eventoX-Nevoa-Delivery-Id— ID da entrega (idempotência)X-Nevoa-Timestamp— Unix epoch (segundos)X-Nevoa-Signature— sha256=<hmac-sha256 hex de `${timestamp}.${corpoBruto}`>User-Agent— Nevoa-PublicAPI/1.0
Política de entrega
- Timeout: 10s
- Retries: 5 tentativas, backoff exponencial (5s, 10s, 20s, 40s, 80s)
- Sucesso: HTTP 2xx
- Idempotência: use X-Nevoa-Delivery-Id / id do envelope
- Integração INACTIVE ou isWebhookEnabled=false → entrega DISABLED
- subscribedEvents ausente ou vazio = todos os eventos, inclusive os lançados no futuro
- HMAC inclui timestamp; rejeite skew > 300s (anti-replay)
Objetos conversation e contact
JSON
{
"conversation": {
"id": "conv-uuid",
"status": "OPEN",
"sector": { "id": "sector-uuid", "name": "Suporte" },
"lastMessage": { "direction": "INBOUND", "at": "2026-08-11T12:00:00.000Z" }
},
"contact": {
"id": "contact-uuid",
"name": "Maria",
"primaryPhoneE164": "+5511888888888",
"lastMessageAt": "2026-08-11T12:00:00.000Z"
}
}- Todos os eventos com conversa/contato incluem esses objetos com o estado atual no momento da emissão.
- conversation.sector: setor atual ({ id, name } ou null).
- conversation.lastMessage: última mensagem visível ({ direction: INBOUND|OUTBOUND, at } ou null). INBOUND = contato enviou; OUTBOUND = equipe enviou. Notas internas não entram.
- contact.lastMessageAt: data ISO da última mensagem enviada pelo contato, em qualquer conversa (null se nunca enviou).
- Os campos conversationId / contactId continuam presentes nos eventos que já os tinham (retrocompatibilidade).
Verificação HMAC
Node.js
const crypto = require('crypto');
function verify(rawBody, secret, signatureHeader, timestampHeader) {
const ts = Number(timestampHeader);
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) {
return false;
}
const expected =
'sha256=' +
crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`, 'utf8')
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Python
import hmac, hashlib, time
def verify(raw_body: bytes, secret: str, signature_header: str, timestamp_header: str) -> bool:
try:
ts = int(timestamp_header)
except (TypeError, ValueError):
return False
if abs(int(time.time()) - ts) > 300:
return False
payload = f"{ts}.".encode() + raw_body
digest = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
expected = f"sha256={digest}"
return hmac.compare_digest(expected, signature_header or "")Catálogo de eventos
Eventos — clique para expandir
Erros
400 — Requisição inválida
Body ou query string fora do contrato: campo obrigatório ausente, enum inválido, filtro não suportado (ex.: assigned=me).
Exemplo
{ "statusCode": 400, "message": ["type must be one of the following values: TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT"], "error": "Bad Request" }401 — Não autorizado
Bearer ausente, inválido ou integração PUBLIC_API inativa.
Exemplo
{ "statusCode": 401, "message": "Token de API inválido ou integração inativa" }404 — Não encontrado (escopo)
Conversa, contato ou canal fora de linkedIntegrationIds. A API responde 404 para não vazar existência.
Exemplo
{ "statusCode": 404, "message": "Conversa não encontrada" }409 — TEMPLATE_REQUIRED
Envio free-form fora da janela de 24h do WhatsApp. Use template.
Exemplo
{
"code": "TEMPLATE_REQUIRED",
"message": "Janela de 24h indisponível. É necessário aguardar resposta do cliente ou usar template."
}429 — Rate limit excedido
Limite de 120 requisições por minuto por integração. Aguarde e tente novamente com backoff.
Exemplo
{ "statusCode": 429, "message": "Limite de requisições da Public API excedido. Tente novamente em instantes." }Changelog
v1 · 2026-10-14
- POST /social/posts/:id/reschedule — muda só a data de um post agendado; com erro, o agendamento atual continua valendo
- POST /social/posts/:id/unschedule — tira da programação e volta para DRAFT, mantendo conteúdo, destinos e data
- POST /social/posts/:id/validate — valida um post salvo, opcionalmente com outra data, sem gravar
- PATCH /social/posts/:id aceita scheduledAt (reagenda post agendado; nos demais status grava a data prevista e null apaga)
- PATCH /social/posts/:id/media/order e DELETE /social/posts/:id/media/:mediaId
- Em post agendado, mudanças de mídia que deixariam a publicação inválida são recusadas com 400 sem gravar
- GET /social/posts: status aceita vários valores separados por vírgula, novo filtro updatedSince e parâmetro order (asc/desc); posts sem data ficam por último
- Novo evento social_post.unscheduled; social_post.scheduled também é emitido quando a data de um post agendado muda
v1 · 2026-10-07
- Publicações Meta: rotas /social para criar, agendar, publicar, cancelar e reenviar posts no Facebook e Instagram
- Vínculo opt-in em config.linkedMetaIntegrationIds (sem vínculo = 403) e allowlist opcional config.linkedMetaAssetIds
- Header Idempotency-Key em POST /social/posts
- Eventos social_post.scheduled, social_post.published, social_post.partially_published, social_post.failed e social_post.canceled
v1 · 2026-08-11
- Todos os eventos de webhook com conversa/contato incluem os objetos conversation e contact com o estado atual
- conversation.sector — setor atual da conversa ({ id, name } ou null)
- conversation.lastMessage — última mensagem visível ({ direction: INBOUND|OUTBOUND, at } ou null); notas internas não contam
- contact.lastMessageAt — data ISO da última mensagem enviada pelo contato (ou null)
- Campo lastMessageAt também na API REST (GET /contacts, GET /contacts/:id, GET /contacts/by-phone/:phone)
v1 · 2026-07-19
- Provider PUBLIC_API com apiToken, webhookSecret, webhookUrl, subscribedEvents, isWebhookEnabled, linkedIntegrationIds
- Webhook de saída (outbox + HMAC + retries) para 8 tipos de evento
- Objeto channel em payloads de mensagem/conversa
- API REST /api/public/v1 (conversas, mensagens, templates, contatos, catálogos, channels, webhook-deliveries)
- POST /messages — free-form por phone/contactId (conversa mais recente no escopo)