Quickstart

Em 5 passos você tem uma instância conectada e enviando mensagens.

  1. Obter API key — Entre em contato com o time comercial para obter as credenciais do seu tenant.
  2. Criar instânciaPOST /instance/create com o nome e a URL do seu webhook.
  3. Escanear QRGET /instance/:name/qr e escanear o código com o celular.
  4. Aguardar conexãoGET /instance/:name/status até receber state: "open".
  5. Enviar primeira mensagemPOST /message/text.
# 1. Criar instância
curl -X POST https://wa.sinapsia.com.ar/instance/create \
  -H "apikey: SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"instanceName":"acme__soporte__main","webhookUrl":"https://meu-saas.com/webhooks/whatsapp","webhookSecret":"meu-segredo-seguro-32"}'

# 2. Obter QR (imagem base64)
curl https://wa.sinapsia.com.ar/instance/acme__soporte__main/qr \
  -H "apikey: SUA_API_KEY"

# 3. Verificar estado (aguardar state: "open")
curl https://wa.sinapsia.com.ar/instance/acme__soporte__main/status \
  -H "apikey: SUA_API_KEY"

# 4. Enviar primeira mensagem
curl -X POST https://wa.sinapsia.com.ar/message/text \
  -H "apikey: SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"instanceName":"acme__soporte__main","to":"5491112345678","text":"Olá! Esta é minha primeira mensagem.","lane":"normal"}'

Autenticação

Todas as requests (exceto GET /health) exigem o header apikey.

apikey: SUA_API_KEY
Header Acesso
apikey: <sua-api-key> Apenas instâncias e recursos do seu tenant

Naming de instâncias

Todas as instâncias seguem o formato:

{tenantId}__{clientId}__{label}

Exemplo:
acme__soporte__main
Regras de formato: Apenas letras minúsculas, números, hifens e underscores. O separador entre segmentos é duplo underscore (__). O sistema valida este formato antes de criar qualquer instância.

Lanes (Anti-ban)

Cada mensagem é enviada por uma das três lanes. A lane controla o delay e os limites de envio para proteger o número do WhatsApp.

Lane Delay Limite/hora Limite/dia Usar para
urgent 1–2s Sem limite Sem limite OTP, alertas críticos
normal 3–8s 80 msg 500 msg Conversação, notificações
bulk 8–15s 30 msg 200 msg Campanhas em massa
Observações importantes:
  • Limites por instância, não por tenant.
  • urgent sempre é processado antes de normal ou bulk.
  • Mensagens que excedem o limite aguardam na fila, não são descartadas.
  • Padrão se não especificado: normal.

Endpoints — Instâncias

POST /instance/create
Cria uma nova instância para um cliente. A instância fica no estado close até que um número seja vinculado escaneando o QR.
Request body
{
  "instanceName": "acme__soporte__main",
  "webhookUrl":    "https://meu-saas.com/webhooks/whatsapp",
  "webhookSecret": "secreto-minimo-16-caracteres"
}
201 Created
{ "instanceName": "acme__soporte__main", "status": "close" }
Erros: 400 validação do body · 409 instância já existe
GET /instance/:name/qr
Retorna o QR code para vincular o número do WhatsApp. O usuário deve abrir o WhatsApp no celular e escanear o código.
200 OK
{ "qrcode": "data:image/png;base64,iVBORw0KGgo..." }
O QR expira em ~40 segundos. Se expirou, chamar o endpoint novamente para obter um novo.
POST /instance/:name/pairing-code/request
Alternativa ao QR. Gera um código de pareamento de 6 dígitos. O usuário insere no WhatsApp → Dispositivos vinculados → Vincular com número de telefone.
Request body
{ "phoneNumber": "5491112345678" }
200 OK
{ "pairingCode": "123456" }
GET /instance/:name/status
Retorna o estado atual da instância. Polling recomendado a cada 3–5s enquanto se aguarda a conexão.
200 OK
{
  "instanceName":       "acme__soporte__main",
  "state":              "open",
  "phoneNumber":        "5491112345678",
  "connectedAt":        "2026-03-15T10:00:00.000Z",
  "messagesQueuedCount": 0
}
stateDescrição
closeNão vinculado. Requer QR ou pairing code.
connectingQR escaneado. Aguardando confirmação.
openConectado e pronto para enviar mensagens.
GET /instance/:name/info
Informações do dispositivo vinculado à instância.
200 OK
{ "device": "iPhone 12", "platform": "WHATSAPP" }
GET /instances
Lista todas as instâncias do seu tenant.
200 OK
[
  { "instanceName": "acme__soporte__main", "state": "open", "phoneNumber": "5491112345678" },
  { "instanceName": "acme__ventas__main",   "state": "close", "phoneNumber": null }
]
GET /tenant/:tenantId/instances
Lista todas as instâncias de um tenant específico.
200 OK
// Array de instâncias do tenant
GET /tenant/:tenantId/client/:clientId/instances
Lista todas as instâncias de um cliente específico dentro de um tenant. Útil para clientes com múltiplos números.
200 OK
// Array de instâncias do cliente
GET /instance/:name/queue
Retorna o estado atual da fila de mensagens da instância, por lane.
200 OK
{ "urgent": 0, "normal": 3, "bulk": 12, "total": 15 }
DELETE /instance/:name
Desconecta o WhatsApp e exclui a instância. As mensagens pendentes na fila são marcadas como falhas.
204 No Content

Endpoints — Mensagens

Todos os endpoints de envio retornam 202 Accepted imediatamente. A mensagem é enfileirada e enviada após o delay anti-ban correspondente à lane.

Resposta compartilhada (todos os endpoints de envio)

{
  "id":       "3b2e1f4a-8c9d-4e2f-b7a1-0d5c6e3f9b2a",
  "status":   "pending",
  "lane":     "normal",
  "queuedAt": "2026-03-15T10:30:00.000Z"
}
POST /message/text
Envia uma mensagem de texto. O campo text suporta spintax.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "text":        "{Olá|Oi}! Seu código é 1234.",
  "lane":        "normal"
}
to: número internacional sem + nem espaços. lane é opcional, padrão normal.
POST /message/image
Envia uma imagem. caption é opcional e suporta spintax.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "imageUrl":    "https://cdn.example.com/foto.jpg",
  "caption":     "Veja isso",
  "lane":        "normal"
}
POST /message/document
Envia um documento. O nome do arquivo é exibido no chat.
{
  "instanceName":  "acme__soporte__main",
  "to":           "5491112345678",
  "documentUrl":  "https://cdn.example.com/fatura.pdf",
  "fileName":     "fatura-001.pdf",
  "caption":      "Sua fatura em anexo",
  "lane":         "normal"
}
POST /message/audio
Envia um áudio como nota de voz (PTT — Push To Talk). Formato recomendado: OGG/Opus.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "audioUrl":    "https://cdn.example.com/audio.ogg",
  "lane":        "normal"
}
POST /message/video
Envia um vídeo. caption é opcional.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "videoUrl":    "https://cdn.example.com/video.mp4",
  "caption":     "Assista",
  "lane":        "normal"
}
POST /message/route
Auto-rota round-robin para clientes com múltiplos números. Seleciona automaticamente qual instância usar. A resposta inclui instanceName para saber qual número foi escolhido.
{
  "tenantId": "acme",
  "clientId": "soporte",
  "to":       "5491112345678",
  "type":     "text",
  "text":     "Olá!",
  "lane":     "normal"
}
type é obrigatório: text | image | document | audio | video. Cada tipo requer seus campos específicos adicionais.
GET /message/:id
Consulta o estado de uma mensagem pelo seu UUID.
200 OK
{
  "id":           "3b2e1f4a-8c9d-4e2f-b7a1-0d5c6e3f9b2a",
  "instanceName": "acme__soporte__main",
  "lane":         "normal",
  "to":           "5491112345678",
  "type":         "text",
  "status":       "sent",
  "createdAt":    "2026-03-15T10:30:00.000Z",
  "sentAt":       "2026-03-15T10:30:07.000Z",
  "error":        null
}
statusDescrição
pendingNa fila, aguardando envio
sentEnviado com sucesso
failedErro — ver campo error

Webhooks

Ao criar uma instância é fornecido um webhookUrl. O gateway envia um POST para essa URL toda vez que ocorre um evento relevante.

Verificação de assinatura HMAC

Cada request inclui o header x-gateway-signature com um HMAC-SHA256 em hex do body bruto, assinado com seu webhookSecret. Sempre verificar a assinatura antes de processar.

const crypto = require('crypto')

function verifySignature(rawBody, secret, signature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')
  return expected === signature
}
import hmac, hashlib

def verify_signature(raw_body: bytes, secret: str, signature: str) -> bool:
    expected = hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return expected == signature

Política de reenvios

O gateway reenvia 3 vezes: delays de 1s, 5s, 30s. Seu endpoint deve responder 2xx rapidamente.

Eventos

instance.connected / instance.disconnected

{
  "event":        "instance.connected",
  "instanceName": "acme__soporte__main",
  "data": {
    "state":  "open",
    "reason": null
  },
  "timestamp": "2026-03-15T10:30:00.000Z"
}

message.received

{
  "event":        "message.received",
  "instanceName": "acme__soporte__main",
  "data": {
    "from":        "5491112345678",
    "pushName":    "João Silva",
    "messageType": "conversation",
    "text":        "Olá, preciso de ajuda",
    "messageId":   "AC3EADB76DB5BE4130602FBF2039604A",
    "timestamp":   1773596760
  },
  "timestamp": "2026-03-15T10:30:00.000Z"
}
  • text é null para mensagens com mídia.
  • Apenas mensagens com fromMe: false são repassadas (não as próprias mensagens enviadas).
  • messageType: conversation, imageMessage, documentMessage, audioMessage, videoMessage.

Monitoramento

Endpoints para consultar estatísticas e gerenciar a fila de mensagens das suas instâncias.

GET /admin/stats
Estatísticas de mensagens por instância.
200 OK
{
  "instances": 2,
  "totals": { "queued": 5, "sent": 1250, "failed": 3 },
  "perInstance": [
    {
      "instanceName": "acme__soporte__main",
      "queue":    { "urgent": 0, "normal": 5, "bulk": 2, "total": 7 },
      "messages": { "sent": 450, "failed": 1, "pending": 7 }
    }
  ]
}
DELETE /admin/queue/:instanceName
Esvazia todas as mensagens pendentes da fila de uma instância.
200 OK
{ "instanceName": "acme__soporte__main", "flushed": 12 }

GET /health

Endpoint de saúde. Não requer autenticação. Útil para monitores e load balancers.

{
  "status":          "ok",
  "tenants":         1,
  "instances":       2,
  "queuedMessages":  5,
  "uptime":          3600
}

Erros

Status Significado Ação
400 Body inválido Ver campo issues na resposta
401 API key ausente ou inválida Verificar header apikey
403 Sem acesso a este recurso O instanceName deve começar com seu tenantId
404 Instância ou mensagem não existe Verificar nome ou ID
409 Instância já existe Usar a existente ou escolher outro nome
502 Erro no serviço do WhatsApp Verificar sessão WhatsApp; tentar novamente
500 Erro interno Reportar ao operador

Formato de erro 400

{
  "error":  "Validation error",
  "issues": [
    {
      "path":    ["instanceName"],
      "message": "Must follow {tenant}__{client}__{label} format"
    }
  ]
}

Spintax

O spintax permite gerar variantes de texto para evitar mensagens repetitivas. É resolvido aleatoriamente no momento do envio.

Sintaxe: {opção1|opção2|opção3} — suporta aninhamento.

# Simples
{Olá|Oi|E aí}! {Como vai|Tudo bem}?
→ "Oi! Tudo bem?"

# Aninhado
{Bom{-dia|a tarde}|Olá}!
→ "Boa tarde!"
Suportado em: campo text (em /message/text) · campo caption (em /message/image, /message/document, /message/video)

Referência rápida

Campo Formato Exemplo
instanceName {tenant}__{client}__{label} acme__soporte__main
to Número internacional sem + 5491112345678
lane urgent / normal / bulk normal
type (route) text / image / document / audio / video text
webhookSecret String, mín. 16 caracteres meu-segredo-seguro-32
Timestamps ISO 8601 UTC 2026-03-15T10:30:00.000Z
IDs de mensagem UUID v4 3b2e1f4a-...

Integrações

Configuração pronta para usar com as plataformas mais populares. Todos os exemplos enviam uma mensagem de texto via POST /message/text — o mesmo padrão se aplica a todos os outros endpoints.

n8n

Adicione um nó HTTP Request e configure assim:

Method:   POST
URL:      https://wa.sinapsia.com.ar/message/text
Auth:     Header Auth  →  Name: apikey  /  Value: {{ $vars.WA_API_KEY }}
Body:     JSON

{
  "instanceName": "acme__suporte__main",
  "to":            "{{ $json.phone }}",
  "text":          "{{ $json.message }}",
  "lane":          "normal"
}

Make (ex-Integromat)

Módulo HTTP → Make a request:

URL:           https://wa.sinapsia.com.ar/message/text
Method:        POST
Headers:       apikey: SUA_API_KEY
Body type:     Raw
Content type:  application/json
Request body:

{
  "instanceName": "acme__suporte__main",
  "to":            "{{1.phone}}",
  "text":          "{{1.message}}",
  "lane":          "normal"
}

Zapier

Ação Webhooks by Zapier → POST:

URL:          https://wa.sinapsia.com.ar/message/text
Payload Type: JSON
Data:
  instanceName  acme__suporte__main
  to            (campo telefone do trigger)
  text          (campo mensagem do trigger)
  lane          normal
Headers:
  apikey        SUA_API_KEY

Power Automate

Adicione uma ação HTTP ao seu fluxo:

Method:  POST
URI:     https://wa.sinapsia.com.ar/message/text
Headers:
  apikey        @{variables('WA_API_KEY')}
  Content-Type  application/json
Body:
{
  "instanceName": "acme__suporte__main",
  "to":            "@{triggerBody()?['phone']}",
  "text":          "@{triggerBody()?['message']}",
  "lane":          "normal"
}

Pipedream

Adicione um step de código Node.js:

import axios from "axios"

export default defineComponent({
  async run({ steps, $ }) {
    const { data } = await axios.post(
      "https://wa.sinapsia.com.ar/message/text",
      {
        instanceName: "acme__suporte__main",
        to:           steps.trigger.event.phone,
        text:         steps.trigger.event.message,
        lane:         "normal",
      },
      { headers: { apikey: process.env.WA_API_KEY } }
    )
    return data
  },
})

Home Assistant

Adicione ao configuration.yaml. Guarde a API key no secrets.yaml.

# configuration.yaml
rest_command:
  send_whatsapp:
    url: https://wa.sinapsia.com.ar/message/text
    method: POST
    headers:
      apikey: !secret wa_api_key
      Content-Type: application/json
    payload: >
      {"instanceName":"acme__suporte__main",
       "to":"{{ to }}",
       "text":"{{ message }}",
       "lane":"normal"}
    content_type: application/json
Chamar com: service: rest_command.send_whatsapp e data: {to: "55...", message: "Olá!"}

Seu backend

// fetch nativo (Node.js 18+)
const res = await fetch("https://wa.sinapsia.com.ar/message/text", {
  method: "POST",
  headers: {
    "apikey": process.env.WA_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    instanceName: "acme__suporte__main",
    to: "5511912345678",
    text: "Olá do Node.js!",
    lane: "normal",
  }),
})
const data = await res.json()
import httpx, os

res = httpx.post(
    "https://wa.sinapsia.com.ar/message/text",
    headers={"apikey": os.environ["WA_API_KEY"]},
    json={
        "instanceName": "acme__suporte__main",
        "to":            "5511912345678",
        "text":          "Olá do Python!",
        "lane":          "normal",
    },
)
data = res.json()
// Guzzle HTTP
$client = new \GuzzleHttp\Client();
$res = $client->post('https://wa.sinapsia.com.ar/message/text', [
    'headers' => ['apikey' => getenv('WA_API_KEY')],
    'json'    => [
        'instanceName' => 'acme__suporte__main',
        'to'           => '5511912345678',
        'text'         => 'Olá do PHP!',
        'lane'         => 'normal',
    ],
]);
$data = json_decode($res->getBody(), true);
import (
    "bytes"; "encoding/json"; "net/http"; "os"
)

payload, _ := json.Marshal(map[string]string{
    "instanceName": "acme__suporte__main",
    "to":           "5511912345678",
    "text":         "Olá do Go!",
    "lane":         "normal",
})
req, _ := http.NewRequest("POST", "https://wa.sinapsia.com.ar/message/text", bytes.NewBuffer(payload))
req.Header.Set("apikey", os.Getenv("WA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)