Sumário

API Pública do Tivar

Documentação dos endpoints públicos e webhooks outbound do Tivar.

Base URL: https://<seu-dominio>
Autenticação: todos os endpoints exigem Authorization: Bearer <workspace_api_key>
Rate limit: 60 requisições por minuto por workspace (exceto /api/external/messages/send — sem rate limit próprio)

Obtenha sua API Key em: Configurações → Workspace → API Key


Endpoints

1. POST /api/v1/send-message

Envia uma mensagem de texto para um número de telefone. Cria o contato e a conversa automaticamente se ainda não existirem.

Canais suportados: WhatsApp Oficial (Meta Cloud API), WhatsApp Lite e WhatsApp via UAZAPI.

Headers

HeaderValor
AuthorizationBearer <workspace_api_key>
Content-Typeapplication/json

Body

CampoTipoObrigatórioDescrição
phonestringNúmero do destinatário. Aceita E.164 (+5511999998888) ou só dígitos com DDD (5511999998888). Mínimo 10 dígitos.
messagestringTexto a enviar. Entre 1 e 4096 caracteres.
channelstring"whatsapp-official" (Meta Cloud API), "uazapi" ou "waha" (WhatsApp Lite).
integrationIdstring (UUID)ID de uma integração específica. Se omitido, usa a integração conectada mais recente do canal escolhido.
agentIdstring (UUID)Atribui a conversa a um agente de IA.
contactNamestringNome do contato exibido no Tivar. Recomendado: se omitido, o contato fica sem nome e o número de telefone é exibido como fallback no chat.

Boa prática para automações: sempre passe contactName com o nome real do lead/cliente. Sem ele, o contato é criado sem nome e fica difícil de identificar no painel.

Exemplo de requisição

curl -X POST https://<seu-dominio>/api/v1/send-message \
  -H "Authorization: Bearer sk_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "5511999998888",
    "message": "Olá! Seu pedido foi confirmado.",
    "channel": "whatsapp-official",
    "contactName": "João Silva"
  }'

Resposta de sucesso — 200 OK

{
  "success": true,
  "conversationId": "uuid-da-conversa",
  "contactId": "uuid-do-contato",
  "messageId": "uuid-da-mensagem",
  "waMessageId": "wamid.xxxxx",
  "contactCreated": false,
  "conversationCreated": false,
  "sent_at": "2026-04-23T10:00:00.000Z"
}
CampoDescrição
conversationIdUUID da conversa no Tivar
contactIdUUID do contato
messageIdUUID da mensagem no banco
waMessageIdID da mensagem retornado pelo provedor
contactCreatedtrue se o contato foi criado agora
conversationCreatedtrue se a conversa foi criada agora

Erros possíveis

StatuserrorO que fazer
400"Parâmetros obrigatórios: phone, message, channel"Verifique os campos obrigatórios.
400"channel inválido. Use \"whatsapp-official\", \"uazapi\" ou \"waha\""Corrija o valor de channel.
502"Erro ao enviar via WhatsApp Lite"Falha no provedor. Tente novamente.
500"Configuração do WhatsApp Lite incompleta"Integração WhatsApp Lite sem credenciais configuradas — reconecte em Configurações → Integrações.
400"phone inválido. Forneça em E.164 ou só dígitos (mínimo 10)."Formate o número corretamente.
400"message deve ser string de 1–4096 caracteres"Mensagem vazia ou muito longa.
400"Integração não está conectada"Reconecte a integração no painel.
401"API Key inválida"Verifique a API Key.
403"Workspace inativo"Workspace suspenso — entre em contato com o suporte.
404"Nenhuma integração \"whatsapp-official\" conectada neste workspace"Conecte uma integração do canal informado.
429"Muitas requisições. Limite: 60/min por workspace."Aguarde e reenvie.
4xx + "requires_template": trueErro de janela 24h (código Meta 131047)O número não interagiu nas últimas 24h. Use /api/v1/send-template com um template HSM aprovado.

2. POST /api/v1/send-template

Envia um template HSM (mensagem estruturada pré-aprovada pela Meta) via WhatsApp Oficial. Único método válido para iniciar contato com números que não interagiram nas últimas 24 horas.

Canal suportado: WhatsApp Oficial (whatsapp-official) apenas.

Headers

HeaderValor
AuthorizationBearer <workspace_api_key>
Content-Typeapplication/json

Body

CampoTipoObrigatórioDescrição
phonestringNúmero do destinatário. Mesmo formato do send-message.
channelstringDeve ser "whatsapp-official".
templateNamestringNome exato do template aprovado no Meta Business Manager.
languagestringCódigo do idioma do template. Padrão: "pt_BR".
variablesarrayLista de valores para substituir as variáveis {{1}}, {{2}} etc. do corpo do template. Pode ser string ou número.
headerMediaobjectNecessário só se o template tiver header de mídia (imagem/vídeo/documento). Ver formato abaixo.
integrationIdstring (UUID)ID de integração específica. Se omitido, usa a conectada mais recente.
agentIdstring (UUID)Atribui a conversa a um agente de IA.
contactNamestringNome do contato exibido no Tivar. Recomendado: se omitido, o contato fica sem nome e o número de telefone é exibido como fallback no chat.

Boa prática para automações (n8n, Make, etc.): sempre passe contactName. Este campo é o que define o nome exibido na conversa dentro do Tivar. Se não for enviado, o painel exibirá apenas o número de telefone no lugar do nome.

Exemplo de requisição

curl -X POST https://<seu-dominio>/api/v1/send-template \
  -H "Authorization: Bearer sk_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "5511999998888",
    "channel": "whatsapp-official",
    "templateName": "confirmacao_pedido",
    "language": "pt_BR",
    "variables": ["João", "Pedido #1234", "R$ 150,00"],
    "contactName": "João Silva"
  }'

As variáveis substituem {{1}}, {{2}}, {{3}} na ordem em que aparecem no array.

Template com header de mídia (imagem/vídeo/documento)

Se o template aprovado tiver um header de mídia, informe headerMedia — sem isso, o envio falha (Meta exige o header preenchido quando o template foi aprovado com ele).

{
  "phone": "5511999998888",
  "channel": "whatsapp-official",
  "templateName": "confirmacao_com_video",
  "language": "pt_BR",
  "variables": ["João"],
  "headerMedia": {
    "type": "video",
    "link": "https://exemplo.com/video-institucional.mp4"
  },
  "contactName": "João Silva"
}
CampoTipoObrigatórioDescrição
headerMedia.typestring"image", "video" ou "document" — deve bater com o tipo de header aprovado no template.
headerMedia.linkstringURL pública (sem autenticação) do arquivo. A Meta baixa direto dessa URL no momento do envio — link expirado/privado falha o envio.
headerMedia.filenamestringSó pra type: "document" — nome do arquivo exibido pro destinatário.

Template com botão dinâmico (URL com sufixo variável, ou quick reply com payload variável)

Só é necessário declarar buttons se o botão tiver parte dinâmica (ex.: link de rastreio único por pedido). Botão totalmente estático (telefone, ou quick reply fixo) já funciona sozinho, sem precisar disso.

{
  "phone": "5511999998888",
  "channel": "whatsapp-official",
  "templateName": "pedido_confirmado",
  "language": "pt_BR",
  "variables": ["João", "Pedido #1234"],
  "buttons": [
    { "index": 0, "type": "url", "text": "pedido-1234" }
  ],
  "contactName": "João Silva"
}
CampoTipoObrigatórioDescrição
buttons[].indexnumberPosição do botão no template, começando em 0 (ordem em que a Meta aprovou).
buttons[].typestring"url" ou "quick_reply".
buttons[].textstring✅ (se type: "url")Valor que substitui a parte variável do link do botão (ex.: template aprovado com link https://seusite.com/pedido/{{1}}text: "1234" gera .../pedido/1234).
buttons[].payloadstring✅ (se type: "quick_reply")Valor dinâmico do payload retornado quando o contato clica no botão.

Resposta de sucesso — 200 OK

{
  "success": true,
  "conversationId": "uuid-da-conversa",
  "contactId": "uuid-do-contato",
  "messageId": "uuid-da-mensagem",
  "waMessageId": "wamid.xxxxx",
  "contactCreated": false,
  "conversationCreated": true,
  "template": {
    "name": "confirmacao_pedido",
    "language": "pt_BR",
    "variables": ["João", "Pedido #1234", "R$ 150,00"],
    "headerMedia": null,
    "buttons": null
  },
  "sent_at": "2026-04-23T10:00:00.000Z"
}

Erros possíveis

Statuserror / meta_error_codeO que fazer
400"Parâmetros obrigatórios: phone, templateName, channel"Verifique os campos.
400"Templates HSM só são suportados pelo canal \"whatsapp-official\""channel deve ser "whatsapp-official".
400"variables deve ser array"Passe um array, mesmo que vazio: [].
400"headerMedia.type inválido..." / "headerMedia.link é obrigatório..."Corrija o objeto headerMedia.
400"buttons deve ser array" / "buttons[].index..." / "buttons[].type..." / "buttons[].text..." / "buttons[].payload..."Corrija o array buttons.
401"API Key inválida"Verifique a API Key.
404"Nenhuma integração WhatsApp Oficial conectada"Conecte a integração no painel.
429"Muitas requisições. Limite: 60/min por workspace."Aguarde e reenvie.
4xx + meta_error_code: 132001Template não encontrado ou não aprovadoConfirme o templateName e o idioma no Meta Business Manager.
4xx + meta_error_code: 132000Número de variáveis incorretoO array variables deve ter exatamente o número de {{N}} do template.
4xx + meta_error_code: 131053Mídia do header inválida/inacessívelConfirme que headerMedia.link é público e o type bate com o aprovado no template.

3. POST /api/external/messages/send

Envia uma mensagem em uma conversa já existente no Tivar. Ideal para integrações como n8n, Typebot e Voiceflow que recebem o conversation_id via webhook e querem responder na mesma conversa.

Canais suportados: WhatsApp Oficial, WhatsApp (UAZAPI), WhatsApp Lite, Telegram, Live Chat.

Diferença em relação ao /api/v1/send-message: este endpoint não cria contato nem conversa — a conversa deve existir previamente. Use quando já tem o conversation_id (por exemplo, recebido num webhook message.received).

Headers

HeaderValor
AuthorizationBearer <workspace_api_key>
Content-Typeapplication/json

Body

CampoTipoObrigatórioDescrição
conversation_idstring (UUID)ID da conversa no Tivar.
textstringTexto da mensagem.
sender_typestring"agent" (padrão) ou "system". Afeta a exibição no histórico.
sender_namestringNome exibido como remetente. Máx. 120 caracteres.

Exemplo de requisição

curl -X POST https://<seu-dominio>/api/external/messages/send \
  -H "Authorization: Bearer sk_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "uuid-da-conversa",
    "text": "Olá! Em que posso ajudar?",
    "sender_type": "agent",
    "sender_name": "Bot Atendimento"
  }'

Resposta de sucesso — 200 OK

{
  "success": true,
  "message_id": "uuid-da-mensagem",
  "conversation_id": "uuid-da-conversa",
  "workspace_id": "uuid-do-workspace",
  "sent_at": "2026-04-23T10:00:00.000Z"
}

Erros possíveis

StatuserrorO que fazer
400"Parâmetros obrigatórios: conversation_id, text"Verifique os campos.
400"sender_type deve ser \"agent\" ou \"system\""Use "agent" ou "system".
400"Integração não está conectada"Reconecte a integração no painel.
401"Authorization header ausente"Adicione o header Authorization.
401"API Key inválida ou não encontrada"Verifique a API Key.
403"Workspace inativo"Workspace suspenso.
404"Conversa não encontrada ou não pertence a este workspace"O conversation_id não existe ou pertence a outro workspace.
502"Erro ao enviar via WhatsApp" / "Erro ao enviar via WhatsApp Lite" / "Erro ao enviar via Telegram"Falha no provedor. Tente novamente.

4. PATCH /api/v1/leads/{id}

Move um contato para outra etapa de um funil (Kanban) sem passar pelo agente de IA. Pensado para integrações externas (n8n, Make) que já decidem o roteamento sozinhas e não devem depender de um agente de IA do Tivar rodando na mesma conversa — evita dois sistemas respondendo o mesmo lead.

Diferença em relação a mover pelo agente de IA: o agente de IA (move_funnel_stage) analisa a conversa e decide sozinho a etapa. Este endpoint espera que você já saiba pipeline_id e stage e só quer aplicar a mudança.

Headers

HeaderValor
AuthorizationBearer <workspace_api_key>
Content-Typeapplication/json

Parâmetro de URL

ParâmetroTipoDescrição
idstring (UUID)ID do contato no Tivar.

Body

CampoTipoObrigatórioDescrição
pipeline_idstring (UUID)ID do funil. Precisa pertencer ao mesmo workspace da API Key.
stagestringChave da etapa dentro do funil (a mesma usada em pipelines.stages_config, ex.: "Aquecendo", "Novo Lead").
conversation_idstring (UUID)Se informado, registra um evento interno (type: system) nessa conversa — visível só pra equipe no Tivar, nunca é enviado ao lead. Sem ele, o contato é movido sem deixar rastro na conversa.

Exemplo de requisição

curl -X PATCH https://<seu-dominio>/api/v1/leads/uuid-do-contato \
  -H "Authorization: Bearer sk_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "pipeline_id": "uuid-do-funil",
    "stage": "Qualificado",
    "conversation_id": "uuid-da-conversa"
  }'

Resposta de sucesso — 200 OK

{
  "success": true,
  "contact_id": "uuid-do-contato",
  "pipeline_id": "uuid-do-funil",
  "stage": "Qualificado"
}

Erros possíveis

StatuserrorO que fazer
400Erro de validação do body (pipeline_id/stage ausentes ou inválidos)Verifique os campos obrigatórios.
400"pipeline_id não encontrado neste workspace"Confirme que o funil pertence ao workspace da API Key usada.
400"stage \"X\" não existe no pipeline \"Y\""Use a chave exata da etapa (mesma de stages_config, não o nome de exibição).
400"conversation_id não pertence a este contato/workspace"Omita conversation_id ou confirme que ela é do mesmo contato.
401"API Key inválida"Verifique a API Key.
404"Contato não encontrado"O id na URL não existe ou pertence a outro workspace.
429"Muitas requisições. Limite: 60/min por workspace."Aguarde e reenvie.

Webhooks Outbound

O Tivar dispara eventos para URLs externas configuradas no painel, permitindo integração com n8n, Typebot, Zapier e outros.

Não confundir com o webhook de um agente de IA. Se você já tem um agente externo (aba "Webhook" dentro do agente) respondendo pelas conversas, não cadastre a mesma URL aqui também — os dois disparam de forma independente, e a mesma mensagem chegaria 2x no seu sistema. Use o webhook genérico só pra eventos que não são sobre "responder o cliente" (log de mensagens, integrações de CRM/BI, notificações internas).

Como configurar

  1. Acesse Configurações → Webhooks.
  2. Clique em Novo webhook.
  3. Informe a URL de destino (deve aceitar POST com JSON).
  4. Selecione os eventos desejados.
  5. Recomendado: informe um segredo para validar a autenticidade dos payloads via HMAC.

Por que configurar o segredo. A URL do seu webhook é um endpoint aberto na internet: sem segredo, qualquer pessoa que descubra esse endereço pode enviar um POST fingindo ser o Tivar, e seu fluxo não tem como diferenciar. Dependendo do que ele faz — criar pedido, disparar cobrança, responder cliente — o estrago é real. Com o segredo configurado, todo payload chega assinado e você rejeita o que não bater.

Headers enviados em todo webhook

HeaderDescrição
Content-Typeapplication/json
X-Webhook-EventNome do evento (ex.: message.received)
X-Webhook-TimestampUnix timestamp (segundos) do envio
X-Webhook-Signaturesha256=<hmac-hex> — presente apenas se um segredo estiver configurado
User-AgentWebhooks/1.0

Compatibilidade. Cada um desses três também é enviado com o prefixo antigo — X-Tivar-Event, X-Tivar-Timestamp e X-Tivar-Signature — com exatamente o mesmo valor. Integrações existentes continuam funcionando sem alteração; em integrações novas, prefira os nomes X-Webhook-*.

Validando a assinatura

Com o segredo configurado, valide assim — e rejeite a requisição quando a assinatura não bater ou o header não vier:

const crypto = require('crypto')

function isValidSignature(body, secret, signatureHeader) {
  // Header ausente = requisição não confiável. Nunca deixe passar.
  if (!signatureHeader) return false

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex')

  const a = Buffer.from(expected)
  const b = Buffer.from(signatureHeader)

  // timingSafeEqual lança exceção se os buffers tiverem tamanhos diferentes —
  // compare o tamanho antes, senão uma assinatura torta derruba seu endpoint.
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

body = string JSON raw do payload recebido (antes de parsear). signatureHeader = valor do header X-Webhook-Signature.

n8n: cuidado com "raw body". O node Webhook do n8n já entrega o payload PARSEADO (objeto JS) pro node seguinte — se um Code node faz JSON.stringify() nesse objeto pra calcular o HMAC, a string quase nunca bate byte a byte com a que o Tivar assinou (ordem de chave, espaçamento, escape) e a assinatura nunca vai validar, mesmo com o segredo certo. Achado real (parceiro white label, 2026-08-24). Fix: no node Webhook, aba Options → Raw Body, ative — aí o Code node recebe a string exata ($input.item.binary.data ou o campo que o n8n expõe pra raw body, dependendo da versão) pra assinar/comparar antes de qualquer parse.


Eventos disponíveis

EventoQuando dispara
message.receivedNova mensagem recebida de um contato
message.sentMensagem enviada pelo Tivar (agente, sistema ou API)
conversation.assignedConversa atribuída ou reatribuída (humano ou agente de IA)
conversation.resolvedConversa marcada como resolvida

Ao configurar o webhook você pode selecionar eventos individuais ou * para receber todos.


message.received

Disparado quando uma nova mensagem chega de um contato externo.

{
  "event": "message.received",
  "workspace_id": "uuid-do-workspace",
  "conversation_id": "uuid-da-conversa",
  "contact": {
    "id": "uuid-do-contato",
    "phone": "5511999998888",
    "name": "João Silva"
  },
  "message": {
    "id": "uuid-da-mensagem",
    "text": "Oi, quero saber sobre meu pedido.",
    "type": "text"
  },
  "status": {
    "assigned_to_type": "agent",
    "assigned_to_id": "uuid-do-agente"
  },
  "integration": {
    "id": "uuid-da-integracao",
    "provider": "whatsapp-official"
  }
}
CampoTipoDescrição
contact.idstring | nullUUID do contato no Tivar
contact.phonestring | nullNúmero normalizado
contact.namestring | nullNome do contato
message.typestringtext, image, audio, video, document, location, etc.
status.assigned_to_type"human" | "agent" | nullTipo do responsável pela conversa
status.assigned_to_idstring | nullUUID do agente/membro responsável
integration.providerstringwhatsapp-official, whatsapp, telegram, instagram, messenger, livechat, mercado_livre

message.sent

Disparado quando o Tivar envia uma mensagem para um contato (via painel, API ou agente de IA).

{
  "event": "message.sent",
  "workspace_id": "uuid-do-workspace",
  "conversation_id": "uuid-da-conversa",
  "message": {
    "id": "uuid-da-mensagem",
    "text": "Seu pedido foi confirmado!",
    "type": "text"
  },
  "status": {
    "sender_type": "human"
  },
  "integration": {
    "id": "uuid-da-integracao",
    "provider": "whatsapp-official"
  }
}

conversation.assigned

Disparado quando uma conversa é atribuída ou reatribuída a um membro humano ou agente de IA. Também disparado ao remover a atribuição (assigned_to_id: null).

{
  "event": "conversation.assigned",
  "workspace_id": "uuid-do-workspace",
  "conversation_id": "uuid-da-conversa",
  "status": {
    "assigned_to_type": "agent",
    "assigned_to_id": "uuid-do-agente"
  }
}
assigned_to_typeCenário
"human"Atribuída a um membro da equipe
"agent"Atribuída a um agente de IA
nullAtribuição removida

conversation.resolved

Disparado quando uma conversa é marcada como resolvida.

{
  "event": "conversation.resolved",
  "workspace_id": "uuid-do-workspace",
  "conversation_id": "uuid-da-conversa",
  "status": {
    "value": "resolved"
  }
}

Fluxo recomendado para automações (n8n / Typebot)

1. Configure um webhook "message.received" no Tivar apontando para seu fluxo n8n.
2. O n8n recebe o payload e processa a mensagem (contact.phone, message.text).
3. Para responder na mesma conversa, use POST /api/external/messages/send
   com o conversation_id recebido no payload.
4. Para iniciar uma conversa nova (disparo ativo), use POST /api/v1/send-message.
5. Para disparar fora da janela 24h do WhatsApp Oficial, use POST /api/v1/send-template.

Resumo rápido

Caso de usoEndpoint
Enviar mensagem de texto para um númeroPOST /api/v1/send-message
Enviar template HSM (fora da janela 24h)POST /api/v1/send-template
Responder numa conversa existentePOST /api/external/messages/send
Mover contato de etapa no funil (sem agente de IA)PATCH /api/v1/leads/{id}
Receber eventos em tempo realWebhooks outbound