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)

Uso responsable

WhatsApp puede banear números que detecta como spam o que violan sus términos. El gateway aplica delays y rate limits automáticamente, pero la responsabilidad final sobre el uso es tuya.

Reglas obligatorias

ReglaDetalle
Un número = una instancia activa No conectes el mismo número en múltiples plataformas simultáneamente. WhatsApp detecta sesiones duplicadas y banea.
Solo contactos con opt-in Enviá mensajes solo a contactos que te escribieron primero o dieron consentimiento explícito. Sin listas compradas ni prospección en frío.
Usá el carril correcto urgent para OTP/alertas, normal para conversaciones, bulk para campañas masivas.
Escalado gradual Números nuevos necesitan historial. Arrancá con volumen bajo durante 2-4 semanas antes de escalar. Nunca campañas masivas desde el día 1.
Contenido permitido No usar para spam, phishing, suplantación de identidad, ni contenido que viole los Términos de Servicio de WhatsApp.

Señales de riesgo

  • WhatsApp muestra "Esta cuenta no puede usar WhatsApp"
  • Los mensajes quedan con un solo tilde y nunca se entregan
  • Contactos reportan no poder enviarte mensajes

Si ves alguna de estas señales, pausá envíos inmediatamente y contactá soporte@sinapsia.com.ar.

Responsabilidades

ResponsabilidadQuién
Delays y rate limits técnicosGateway (automático)
Qué mensajes se envían y a quiénVos (el cliente)
Estado del número (ban o no)WhatsApp
Soporte técnicosoporte@sinapsia.com.ar

SinapsIA no se responsabiliza por baneos derivados del uso indebido del servicio.