Documentação da API
A API Zivlo permite enviar mensagens WhatsApp e disparar fluxos de automação programaticamente. Use-a para integrar com seus sistemas, CRMs, plataformas de agendamento e muito mais.
Content-Type: application/json em todas as requisições.
🔐 Autenticação
A API usa tokens Bearer para autenticação. Inclua o token no header de cada requisição:
Authorization: Bearer whsp_sua_chave_aqui
Alternativamente, use o header X-API-Token:
X-API-Token: whsp_sua_chave_aqui
Formato do token
Todos os tokens seguem o padrão:
whsp_<40 caracteres hexadecimais> Exemplo: whsp_a3f8d2e1c9b74f620a8e5d3c1b9f7e4a2d6c8b5
⚡ Respostas de erro
Todos os erros seguem o mesmo formato JSON:
{
"error": "Descrição do erro"
}
Códigos HTTP
🚦 Rate Limit
Os endpoints públicos /v1/* não possuem rate limiting por padrão. O rate limit de 100 req/min aplica-se apenas às rotas internas do painel.
Para ambientes de produção, recomendamos implementar throttling no seu lado para evitar sobrecarga da fila de mensagens.
📨 Enviar mensagem
Envia uma mensagem WhatsApp (texto, mídia ou template) via uma conta conectada.
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| account_id | uuid | Sim | UUID da conta WhatsApp conectada no Zivlo |
| to | string | Sim | Número do destinatário com DDI. Apenas dígitos (ex: 5511999998888) |
| type | string | Sim | Tipo: text · image · video · audio · document · template |
| messaging_product | string | Sim | Sempre "whatsapp" |
| text.body | string | Cond. | Corpo da mensagem quando type = "text" |
| image.link | string | Cond. | URL pública da imagem quando type = "image" |
| image.caption | string | Não | Legenda da imagem (opcional) |
| video.link | string | Cond. | URL pública do vídeo quando type = "video" |
| audio.link | string | Cond. | URL pública do áudio quando type = "audio" |
| document.link | string | Cond. | URL pública do documento quando type = "document" |
| document.filename | string | Não | Nome do arquivo a exibir no WhatsApp |
| template.name | string | Cond. | Nome do template aprovado pela Meta (apenas Cloud API) |
| template.language.code | string | Cond. | Ex: "pt_BR" — código de idioma do template |
| template.components | array | Não | Variáveis de preenchimento do template (header, body, buttons) |
Mensagem de texto
curl -X POST https://api.zivlo.com.br/v1/messages/send \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "account_id": "uuid-da-conta", "messaging_product": "whatsapp", "to": "5511999998888", "type": "text", "text": { "body": "Olá! Sua consulta foi confirmada." } }'
Imagem com legenda
curl -X POST https://api.zivlo.com.br/v1/messages/send \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "account_id": "uuid-da-conta", "messaging_product": "whatsapp", "to": "5511999998888", "type": "image", "image": { "link": "https://exemplo.com/imagem.jpg", "caption": "Veja nossa novidade!" } }'
Template (Cloud API)
curl -X POST https://api.zivlo.com.br/v1/messages/send \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "account_id": "uuid-da-conta", "messaging_product": "whatsapp", "to": "5511999998888", "type": "template", "template": { "name": "confirmacao_consulta", "language": { "code": "pt_BR" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "João Silva" }, { "type": "text", "text": "20/03/2026 às 14h" } ] } ] } }'
const response = await fetch('https://api.zivlo.com.br/v1/messages/send', { method: 'POST', headers: { 'Authorization': 'Bearer whsp_sua_chave', 'Content-Type': 'application/json', }, body: JSON.stringify({ account_id: 'uuid-da-conta', messaging_product: 'whatsapp', to: '5511999998888', type: 'text', text: { body: 'Olá! Sua consulta foi confirmada.' }, }), }); const data = await response.json(); console.log(data); // { ok: true, wa_message_id: "wamid.xxx" }
import requests url = "https://api.zivlo.com.br/v1/messages/send" headers = { "Authorization": "Bearer whsp_sua_chave", "Content-Type": "application/json", } payload = { "account_id": "uuid-da-conta", "messaging_product": "whatsapp", "to": "5511999998888", "type": "text", "text": {"body": "Olá! Sua consulta foi confirmada."}, } response = requests.post(url, json=payload, headers=headers) print(response.json()) # {'ok': True, 'wa_message_id': 'wamid.xxx'}
Sucesso (200)
// Baileys { "ok": true } // Cloud API { "ok": true, "wa_message_id": "wamid.HBgNNTUxMTk5OTk5ODg4FQIAERgSM..." }
Erros possíveis
account_id must be a valid UUID · "to" must contain only digits · type is requiredInvalid or expired API tokenAccount is bannedAccount not foundBaileys account not connected⚡ Disparar fluxo
Inicia uma sessão de automação para um contato, como se ele tivesse enviado a mensagem gatilho. Ideal para campanhas, agendamentos e integrações com outros sistemas.
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| account_id | uuid | Sim | UUID da conta WhatsApp que enviará as mensagens do fluxo |
| flow_id | uuid | Sim | UUID do fluxo a ser executado. O fluxo deve estar ativo (is_active = true) |
| to | string | Não* | Número do destinatário com DDI. Se omitido, usa variables.phone |
| variables | object | Não | Variáveis iniciais injetadas no fluxo. Mescladas com phone, date e time |
| force | boolean | Não | Se true, cancela sessão ativa existente e reinicia o fluxo. Padrão: false |
* to ou variables.phone são obrigatórios.
Variáveis automáticas
As seguintes variáveis são sempre injetadas na sessão, independente do que você enviar:
| Variável | Exemplo | Descrição |
|---|---|---|
| {{phone}} | 5511999998888 | Número do destinatário |
| {{date}} | 20/03/2026 | Data atual (formato pt-BR) |
| {{time}} | 14:32:08 | Hora atual (formato pt-BR) |
force: true para cancelar a sessão existente e iniciar uma nova.
Disparar fluxo simples
curl -X POST https://api.zivlo.com.br/v1/flows/trigger \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "account_id": "uuid-da-conta", "flow_id": "uuid-do-fluxo", "to": "5511999998888" }'
Com variáveis personalizadas
curl -X POST https://api.zivlo.com.br/v1/flows/trigger \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "account_id": "uuid-da-conta", "flow_id": "uuid-do-fluxo", "to": "5511999998888", "variables": { "nome_paciente": "João Silva", "data_consulta": "20/03/2026", "horario": "14h30", "medico": "Dr. Carlos" } }'
Forçar reinício de sessão
curl -X POST https://api.zivlo.com.br/v1/flows/trigger \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "account_id": "uuid-da-conta", "flow_id": "uuid-do-fluxo", "to": "5511999998888", "force": true }'
// Disparar fluxo com variáveis de agendamento async function triggerAppointmentFlow(phone, appointment) { const response = await fetch('https://api.zivlo.com.br/v1/flows/trigger', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.ZIVLO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ account_id: process.env.ZIVLO_ACCOUNT_ID, flow_id: 'uuid-do-fluxo-consulta', to: phone, variables: { nome_paciente: appointment.patientName, data_consulta: appointment.date, horario: appointment.time, medico: appointment.doctor, }, }), }); if (!response.ok) { const err = await response.json(); throw new Error(err.error); } return await response.json(); // { ok: true, session_id: "uuid-da-sessao" } } // Uso com webhook de agendamento app.post('/webhook/agendamento', async (req, res) => { const { phone, ...appointment } = req.body; try { const result = await triggerAppointmentFlow(phone, appointment); res.json({ success: true, session_id: result.session_id }); } catch (e) { res.status(500).json({ error: e.message }); } });
import requests import os def trigger_flow(phone: str, flow_id: str, variables: dict = None, force: bool = False): """Dispara um fluxo Zivlo para um número de WhatsApp.""" url = "https://api.zivlo.com.br/v1/flows/trigger" headers = { "Authorization": f"Bearer {os.environ['ZIVLO_API_KEY']}", "Content-Type": "application/json", } payload = { "account_id": os.environ["ZIVLO_ACCOUNT_ID"], "flow_id": flow_id, "to": phone, "force": force, } if variables: payload["variables"] = variables response = requests.post(url, json=payload, headers=headers) response.raise_for_status() return response.json() # Exemplo: Notificar paciente de consulta result = trigger_flow( phone="5511999998888", flow_id="uuid-fluxo-consulta", variables={ "nome_paciente": "João Silva", "data_consulta": "20/03/2026", "horario": "14h30", } ) print(result) # {'ok': True, 'session_id': 'uuid-da-sessao'}
Sucesso (200)
{
"ok": true,
"session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
Erros possíveis
account_id must be a valid UUID · flow_id must be a valid UUIDInvalid or expired API tokenAccount not found · Flow not found or not active · Flow does not belong to your tenantPhone already has an active flow session. Pass "force": trueAccount not connected (Baileys desconectado)📒 Criar lista de contatos
Cria uma nova lista de contatos ou retorna a lista existente (se já houver uma com o mesmo nome no seu tenant). Útil antes de importar contatos com /v1/contacts/import.
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| name | string | Sim | Nome da lista. Único por tenant (máx 200 caracteres) |
| description | string | Não | Descrição opcional (máx 2000 caracteres) |
created: false e devolve a lista existente (status 200). Quando a lista é criada do zero, o status é 201 Created.
curl -X POST https://api.zivlo.com.br/v1/contacts/lists \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Clientes Black Friday", "description": "Campanha de novembro 2026" }'
const res = await fetch('https://api.zivlo.com.br/v1/contacts/lists', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.ZIVLO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Clientes Black Friday', description: 'Campanha de novembro 2026', }), }); const { list, created } = await res.json(); console.log('List ID:', list.id, '(new:', created, ')');
import os, requests res = requests.post( "https://api.zivlo.com.br/v1/contacts/lists", headers={ "Authorization": f"Bearer {os.environ['ZIVLO_API_KEY']}", "Content-Type": "application/json", }, json={ "name": "Clientes Black Friday", "description": "Campanha de novembro 2026", }, ) res.raise_for_status() list_id = res.json()["list"]["id"] print("List ID:", list_id)
Sucesso (201 — criada) / (200 — existente)
{
"ok": true,
"created": true,
"list": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Clientes Black Friday",
"description": "Campanha de novembro 2026",
"contact_count": 0,
"created_at": "2026-04-24T12:34:56Z"
}
}
Erros possíveis
name is required and must be a non-empty stringInvalid or revoked API token📥 Importar contatos
Importa contatos em massa para uma lista. Números são normalizados (apenas dígitos) antes da gravação. Aceita até 1000 contatos por requisição e faz upsert por (list_id, phone) — a mesma chamada pode criar e atualizar ao mesmo tempo.
Campos da requisição
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| list_id | uuid | Cond.* | UUID de uma lista existente. Use este OU list_name |
| list_name | string | Cond.* | Nome da lista. Cria automaticamente se não existir (controlado por create_list_if_missing) |
| create_list_if_missing | boolean | Não | Padrão true. Quando false, retorna 404 se a lista nomeada não existir |
| upsert | boolean | Não | Padrão true. Quando false, contatos já existentes são pulados (não atualizados) |
| contacts | array | Sim | Lista de contatos a importar (1 até 1000 itens) |
* Informe um entre list_id ou list_name.
Campos de cada contato
| Campo | Tipo | Req. | Descrição |
|---|---|---|---|
| phone | string | Sim | Telefone com DDI. Aceita qualquer formato — tudo que não é dígito é removido. Mínimo 8 dígitos |
| name | string | Não | Nome de exibição (máx 200) |
| string | Não | E-mail (máx 320) | |
| tags | string[] | Não | Até 20 tags, cada uma com até 64 caracteres |
| notes | string | Não | Observações internas (máx 2000) |
| birth_date | string | Não | Data de nascimento no formato YYYY-MM-DD |
| avatar_url | string | Não | URL pública da foto de perfil |
| custom_fields | object | Não | Objeto plano com campos customizados (ex: { "cidade": "SP", "cpf": "..." }). Ficam disponíveis como variáveis de fluxo |
"+55 (11) 99999-8888" → "5511999988888". Duplicatas dentro da mesma requisição são reportadas em errors.
errors e os demais são processados normalmente.
Importação básica
curl -X POST https://api.zivlo.com.br/v1/contacts/import \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "list_name": "Clientes Black Friday", "contacts": [ { "phone": "+55 11 99999-8888", "name": "João Silva", "email": "joao@example.com" }, { "phone": "5511999997777", "name": "Maria Souza", "tags": ["vip", "whatsapp-ads"] } ] }'
Com campos customizados e upsert desativado
curl -X POST https://api.zivlo.com.br/v1/contacts/import \ -H "Authorization: Bearer whsp_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "list_id": "uuid-da-lista", "upsert": false, "contacts": [ { "phone": "5511999998888", "name": "João Silva", "birth_date": "1990-05-20", "custom_fields": { "cidade": "São Paulo", "produto_interesse": "plano-anual" } } ] }'
// Importa contatos vindos de um CSV / export de CRM async function importContacts(listName, contacts) { const res = await fetch('https://api.zivlo.com.br/v1/contacts/import', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.ZIVLO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ list_name: listName, upsert: true, contacts, }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error); } return await res.json(); } // Dica: divida em lotes de 500 para listas muito grandes function chunk(arr, size = 500) { const out = []; for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)); return out; } const allContacts = [ { phone: '+55 11 99999-8888', name: 'João Silva', tags: ['vip'] }, { phone: '5511999997777', name: 'Maria Souza' }, // ... N contatos ]; for (const batch of chunk(allContacts, 500)) { const result = await importContacts('Clientes Black Friday', batch); console.log(result); // { inserted, updated, skipped, failed, errors } }
import csv, os, requests from itertools import islice URL = "https://api.zivlo.com.br/v1/contacts/import" TOKEN = os.environ["ZIVLO_API_KEY"] def chunks(iterable, size=500): it = iter(iterable) while batch := list(islice(it, size)): yield batch def import_contacts(list_name, contacts): res = requests.post( URL, headers={"Authorization": f"Bearer {TOKEN}"}, json={"list_name": list_name, "upsert": True, "contacts": contacts}, ) res.raise_for_status() return res.json() # Carrega um CSV com colunas: phone, name, email, cidade with open("contacts.csv") as f: rows = [ { "phone": r["phone"], "name": r["name"], "email": r["email"], "custom_fields": {"cidade": r["cidade"]}, } for r in csv.DictReader(f) ] for batch in chunks(rows, 500): result = import_contacts("Clientes Black Friday", batch) print(result) # {'inserted': 420, 'updated': 60, 'skipped': 0, 'failed': 20, 'errors': [...]}
Sucesso (200)
{
"ok": true,
"list_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"received": 500,
"inserted": 420,
"updated": 60,
"skipped": 0,
"failed": 20,
"errors": [
{ "index": 17, "phone": "+1 555",
"error": "phone has fewer than 8 digits after normalization" },
{ "index": 92, "phone": "5511999998888",
"error": "duplicate phone in the same request" }
]
}
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
| list_id | uuid | UUID da lista (criada ou reutilizada) |
| received | integer | Total de itens enviados no contacts |
| inserted | integer | Novos contatos criados |
| updated | integer | Contatos já existentes que foram atualizados (apenas quando upsert: true) |
| skipped | integer | Contatos pulados por já existirem (apenas quando upsert: false) |
| failed | integer | Contatos inválidos ou não gravados |
| errors | array | Detalhes de cada falha: { index, phone?, error } |
Erros possíveis
contacts must be an array · Too many contacts in a single request (max 1000) · Either list_id or list_name is required · No valid contacts to importInvalid or revoked API tokenList not found or does not belong to your tenantUpsert failed: ... · Insert failed: ...🗝️ Como gerar um token de API
Tokens de API estão disponíveis para planos Pro e superiores.
Authorization: Bearer whsp_... de todas as chamadas à API.ZIVLO_API_KEY) sempre que possível.
📎 Tipos de mensagem
Compatibilidade por tipo de conta
| Tipo | Baileys | Cloud API | Observação |
|---|---|---|---|
| text | ✓ | ✓ | Suporta emojis e markdown WhatsApp (*bold*, _italic_) |
| image | ✓ | ✓ | JPEG, PNG, WebP. Máx 5 MB (Cloud API) / 16 MB (Baileys) |
| video | ✓ | ✓ | MP4 com codec H.264. Máx 16 MB |
| audio | ✓ | ✓ | MP3, OGG, AAC. Enviado como áudio de voz no Cloud API |
| document | ✓ | ✓ | PDF, DOCX, XLSX, etc. Inclua filename para exibir nome |
| template | ✗ | ✓ | Requer template aprovado pela Meta. Necessário para iniciar conversas |
🧩 Variáveis de fluxo
Ao disparar um fluxo via API, você pode injetar variáveis que ficam disponíveis em todos os nós usando a sintaxe {{nome_variavel}}.
Exemplo: confirmação de consulta
Olá, {{nome_paciente}}! 👋 Sua consulta com {{medico}} está confirmada: 📅 Data: {{data_consulta}} 🕐 Horário: {{horario}} Responda 1 para confirmar ou 2 para cancelar.
POST /v1/flows/trigger
{
"account_id": "...",
"flow_id": "uuid-do-fluxo-consulta",
"to": "5511999998888",
"variables": {
"nome_paciente": "João Silva",
"medico": "Dr. Carlos Alves",
"data_consulta": "20/03/2026",
"horario": "14h30"
}
}
Variáveis capturadas por nós "Fazer Pergunta" e "Agente IA" durante a sessão também ficam disponíveis nos nós seguintes usando a mesma sintaxe.