Quickstart

En 5 pasos tenés una instancia conectada y enviando mensajes.

  1. Obtener API key — Contactar a ventas para obtener las credenciales de tu tenant.
  2. Crear instanciaPOST /instance/create con el nombre y tu webhook URL.
  3. Escanear QRGET /instance/:name/qr y escanear el código con el celular.
  4. Esperar conexiónGET /instance/:name/status hasta recibir state: "open".
  5. Enviar primer mensajePOST /message/text.
# 1. Crear instancia
curl -X POST https://wa.sinapsia.com.ar/instance/create \
  -H "apikey: TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"instanceName":"acme__soporte__main","webhookUrl":"https://mi-saas.com/webhooks/whatsapp","webhookSecret":"mi-secreto-seguro-32"}'

# 2. Obtener QR (imagen base64)
curl https://wa.sinapsia.com.ar/instance/acme__soporte__main/qr \
  -H "apikey: TU_API_KEY"

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

# 4. Enviar primer mensaje
curl -X POST https://wa.sinapsia.com.ar/message/text \
  -H "apikey: TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"instanceName":"acme__soporte__main","to":"5491112345678","text":"Hola! Este es mi primer mensaje.","lane":"normal"}'

Autenticación

Todas las requests (salvo GET /health) requieren el header apikey.

apikey: TU_API_KEY
Header Acceso
apikey: <tu-api-key> Solo instancias y recursos de tu tenant

Naming de instancias

Todas las instancias siguen el formato:

{tenantId}__{clientId}__{label}

Ejemplo:
acme__soporte__main
Reglas de formato: Solo minúsculas, números, guiones y guiones bajos. El separador entre segmentos es doble guión bajo (__). El sistema valida este formato antes de crear cualquier instancia.

Carriles (Anti-ban)

Cada mensaje se envía por uno de tres carriles. El carril controla el delay y los límites de envío para proteger el número de WhatsApp.

Lane Delay Límite/hora Límite/día Usar para
urgent 1–2s Sin límite Sin límite OTP, alertas críticas
normal 3–8s 80 msg 500 msg Conversación, notificaciones
bulk 8–15s 30 msg 200 msg Campañas masivas
Notas importantes:
  • Límites por instancia, no por tenant.
  • urgent siempre se procesa antes que normal o bulk.
  • Mensajes que exceden el límite esperan en cola, no se descartan.
  • Default si no se especifica: normal.

Endpoints — Instancias

POST /instance/create
Crea una nueva instancia para un cliente. La instancia queda en estado close hasta que se vincula un número escaneando el QR.
Request body
{
  "instanceName": "acme__soporte__main",
  "webhookUrl":    "https://mi-saas.com/webhooks/whatsapp",
  "webhookSecret": "secreto-minimo-16-caracteres"
}
201 Created
{ "instanceName": "acme__soporte__main", "status": "close" }
Errores: 400 validación de body · 409 la instancia ya existe
GET /instance/:name/qr
Retorna el QR code para vincular el número de WhatsApp. El usuario debe abrir WhatsApp en su celular y escanear el código.
200 OK
{ "qrcode": "data:image/png;base64,iVBORw0KGgo..." }
El QR expira en ~40 segundos. Si expiró, llamar nuevamente al endpoint para obtener uno nuevo.
POST /instance/:name/pairing-code/request
Alternativa al QR. Genera un código de emparejamiento de 6 dígitos. El usuario lo ingresa en WhatsApp → Dispositivos vinculados → Vincular con número de teléfono.
Request body
{ "phoneNumber": "5491112345678" }
200 OK
{ "pairingCode": "123456" }
GET /instance/:name/status
Retorna el estado actual de la instancia. Polling recomendado cada 3–5s mientras se espera la conexión.
200 OK
{
  "instanceName":       "acme__soporte__main",
  "state":              "open",
  "phoneNumber":        "5491112345678",
  "connectedAt":        "2026-03-15T10:00:00.000Z",
  "messagesQueuedCount": 0
}
stateDescripción
closeNo vinculado. Requiere QR o pairing code.
connectingQR escaneado. Esperando confirmación.
openConectado y listo para enviar mensajes.
GET /instance/:name/info
Información del dispositivo vinculado a la instancia.
200 OK
{ "device": "iPhone 12", "platform": "WHATSAPP" }
GET /instances
Lista todas las instancias de tu 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 las instancias de un tenant específico.
200 OK
// Array de instancias del tenant
GET /tenant/:tenantId/client/:clientId/instances
Lista todas las instancias de un cliente específico dentro de un tenant. Útil para clientes con múltiples números.
200 OK
// Array de instancias del cliente
GET /instance/:name/queue
Retorna el estado actual de la cola de mensajes de la instancia, por carril.
200 OK
{ "urgent": 0, "normal": 3, "bulk": 12, "total": 15 }
DELETE /instance/:name
Desconecta WhatsApp y elimina la instancia. Los mensajes pendientes en cola se marcan como fallidos.
204 No Content

Endpoints — Mensajes

Todos los endpoints de envío retornan 202 Accepted inmediatamente. El mensaje se encola y se envía después del delay anti-ban correspondiente al carril.

Respuesta compartida (todos los endpoints de envío)

{
  "id":       "3b2e1f4a-8c9d-4e2f-b7a1-0d5c6e3f9b2a",
  "status":   "pending",
  "lane":     "normal",
  "queuedAt": "2026-03-15T10:30:00.000Z"
}
POST /message/text
Envía un mensaje de texto. El campo text soporta spintax.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "text":        "{Hola|Hey}! Tu código es 1234.",
  "lane":        "normal"
}
to: número internacional sin + ni espacios. lane es opcional, default normal.
POST /message/image
Envía una imagen. caption es opcional y soporta spintax.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "imageUrl":    "https://cdn.example.com/foto.jpg",
  "caption":     "Mirá esto",
  "lane":        "normal"
}
POST /message/document
Envía un documento. Se muestra el nombre de archivo en el chat.
{
  "instanceName":  "acme__soporte__main",
  "to":           "5491112345678",
  "documentUrl":  "https://cdn.example.com/factura.pdf",
  "fileName":     "factura-001.pdf",
  "caption":      "Tu factura adjunta",
  "lane":         "normal"
}
POST /message/audio
Envía un audio 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
Envía un video. caption es opcional.
{
  "instanceName": "acme__soporte__main",
  "to":          "5491112345678",
  "videoUrl":    "https://cdn.example.com/video.mp4",
  "caption":     "Miralo",
  "lane":        "normal"
}
POST /message/route
Auto-ruta round-robin para clientes con múltiples números. Selecciona automáticamente qué instancia usar. La respuesta incluye instanceName para saber qué número se eligió.
{
  "tenantId": "acme",
  "clientId": "soporte",
  "to":       "5491112345678",
  "type":     "text",
  "text":     "Hola!",
  "lane":     "normal"
}
type es requerido: text | image | document | audio | video. Cada tipo requiere sus campos específicos adicionales.
GET /message/:id
Consulta el estado de un mensaje por su 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
}
statusDescripción
pendingEn cola, esperando ser enviado
sentEnviado correctamente
failedError — ver campo error

Webhooks

Al crear una instancia se provee un webhookUrl. El gateway envía un POST a esa URL cada vez que ocurre un evento relevante.

Verificación de firma HMAC

Cada request incluye el header x-gateway-signature con un HMAC-SHA256 en hex del body crudo, firmado con tu webhookSecret. Siempre verificar la firma antes de procesar.

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 reintentos

El gateway reintenta 3 veces: delays de 1s, 5s, 30s. Tu endpoint debe responder 2xx rápidamente.

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":    "Juan Pérez",
    "messageType": "conversation",
    "text":        "Hola, necesito ayuda",
    "messageId":   "AC3EADB76DB5BE4130602FBF2039604A",
    "timestamp":   1773596760
  },
  "timestamp": "2026-03-15T10:30:00.000Z"
}
  • text es null para mensajes con media.
  • Solo se reenvían mensajes con fromMe: false (no los propios mensajes enviados).
  • messageType: conversation, imageMessage, documentMessage, audioMessage, videoMessage.

Monitoreo

Endpoints para consultar estadísticas y gestionar la cola de mensajes de tus instancias.

GET /admin/stats
Estadísticas de mensajes por instancia.
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
Vacía todos los mensajes pendientes de la cola de una instancia.
200 OK
{ "instanceName": "acme__soporte__main", "flushed": 12 }

GET /health

Endpoint de salud. No requiere autenticación. Útil para monitores y load balancers.

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

Errores

Status Significado Acción
400 Body inválido Ver campo issues en respuesta
401 API key faltante o inválida Verificar header apikey
403 Sin acceso a este recurso El instanceName debe empezar con tu tenantId
404 Instancia o mensaje no existe Verificar nombre o ID
409 Instancia ya existe Usar la existente o elegir otro nombre
502 Error en el servicio de WhatsApp Verificar sesión WhatsApp; reintentar
500 Error interno Reportar al operador

Formato de error 400

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

Spintax

El spintax permite generar variantes de texto para evitar mensajes repetitivos. Se resuelve aleatoriamente en el momento del envío.

Sintaxis: {opción1|opción2|opción3} — soporta anidamiento.

# Simple
{Hola|Hey|Buenas}! {Como estás|Qué tal}?
→ "Hey! Qué tal?"

# Anidado
{Buen{os días|as tardes}|Hola}!
→ "Buenas tardes!"
Soportado en: campo text (en /message/text) · campo caption (en /message/image, /message/document, /message/video)

Referencia rápida

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

Integraciones

Configuración lista para usar con las plataformas más populares. Todos los ejemplos envían un mensaje de texto vía POST /message/text — el mismo patrón aplica al resto de endpoints.

n8n

Agregar un nodo HTTP Request y configurarlo con los siguientes parámetros:

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

{
  "instanceName": "acme__soporte__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: TU_API_KEY
Body type:     Raw
Content type:  application/json
Request body:

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

Zapier

Acción Webhooks by Zapier → POST:

URL:          https://wa.sinapsia.com.ar/message/text
Payload Type: JSON
Data:
  instanceName  acme__soporte__main
  to            (campo teléfono del trigger)
  text          (campo mensaje del trigger)
  lane          normal
Headers:
  apikey        TU_API_KEY

Tu 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__soporte__main",
    to: "5491112345678",
    text: "Hola desde 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__soporte__main",
        "to":            "5491112345678",
        "text":          "Hola desde 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__soporte__main',
        'to'           => '5491112345678',
        'text'         => 'Hola desde PHP!',
        'lane'         => 'normal',
    ],
]);
$data = json_decode($res->getBody(), true);
import (
    "bytes"; "encoding/json"; "net/http"; "os"
)

payload, _ := json.Marshal(map[string]string{
    "instanceName": "acme__soporte__main",
    "to":           "5491112345678",
    "text":         "Hola desde 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)