API pública y Webhooks
Synvaxis expone una API REST en el namespace /api/v1/ y un sistema de webhooks salientes firmados con HMAC-SHA256 para que tus sistemas (CRM, ERP, app interna) reaccionen en tiempo real a eventos del bot.
Disponibilidad por plan #
API y webhooks están incluidos en todos los planes activos. La diferencia entre planes es el rate limit por clave:
| Capacidad | Starter | Pro | Business |
|---|---|---|---|
| API key con scope `read` | ✅ | ✅ | ✅ |
| API key con scope `write` | ✅ | ✅ | ✅ |
| Webhooks salientes | ✅ | ✅ | ✅ |
| Rate limit por clave | 60 / min | 120 / min | 600 / min |
> Las claves se generan desde Developers en el dashboard (sidebar). Cada clave se muestra una sola vez al crearla — guárdala antes de cerrar el modal.
Autenticación #
Todas las llamadas a /api/v1/* requieren el header:
Authorization: Bearer syn_<tu_clave>Las claves comienzan con syn_ seguido de 40 caracteres hexadecimales. Sólo guardamos un hash sha-256; si la pierdes, debes revocarla y generar una nueva.
Headers que devuelve la API
Cada respuesta incluye:
x-request-id: 9f3c… # útil para reportar incidencias a soporte
x-ratelimit-limit: 60 # techo en la ventana de 60s
x-ratelimit-remaining: 54
x-ratelimit-reset: 2026-04-27T12:34:56.000Z
Si superas el rate limit, recibes 429 con header retry-after en segundos.
Endpoints #
Todos los endpoints GET aceptan ?limit=N (1-200, default 50) y ?cursor= para paginación. La respuesta tiene la forma:
{ "data": [ … ], "next_cursor": "<id|null>" }| Método | Ruta | Scope | Descripción |
|---|---|---|---|
| `GET` | `/api/v1/_meta` | read | Identidad de la clave + plan + rate limit |
| `GET` | `/api/v1/contacts` | read | Lista contactos del tenant |
| `POST` | `/api/v1/contacts` | write | Crea un contacto |
| `GET` | `/api/v1/conversations` | read | Lista conversaciones (filtros: `platform`, `is_bot_active`) |
| `GET` | `/api/v1/conversations/{id}` | read | Detalle de una conversación |
| `GET` | `/api/v1/messages?conversation_id=…` | read | Mensajes de una conversación |
| `GET` | `/api/v1/orders` | read | Lista órdenes (filtros: `status`, `payment_status`, `conversation_id`) |
| `GET` | `/api/v1/orders/{id}` | read | Detalle de una orden |
| `GET` | `/api/v1/products` | read | Lista productos (filtro: `is_active`) |
| `GET` | `/api/v1/products/{id}` | read | Detalle de producto (incluye `add_ons`, `knowledge_base`) |
| `GET` | `/api/v1/appointments` | read | Lista citas (filtros: `status`, `from`, `to`) |
| `GET` | `/api/v1/appointments/{id}` | read | Detalle de cita |
Ejemplo — listar contactos
curl https://app.synvaxis.com/api/v1/contacts?limit=10 \
-H "Authorization: Bearer syn_abcd…"
{
"data": [
{ "id": "…", "phone": "+5215512345678", "name": "María", "status": "active", … }
],
"next_cursor": null
}
Ejemplo — crear contacto (write)
curl -X POST https://app.synvaxis.com/api/v1/contacts \
-H "Authorization: Bearer syn_xyz…" \
-H "Content-Type: application/json" \
-d '{"phone":"+5215512345678","name":"María"}'
Forma de los errores
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded (60 requests / 60s). Retry after 2026-04-27T12:34:56.000Z.",
"request_id": "9f3c…"
}
}
Códigos posibles: unauthorized (401), forbidden (403), not_found (404), invalid_request (400), conflict (409), rate_limited (429), internal (500).
Webhooks salientes (Business) #
En lugar de hacer polling, registra una URL HTTPS y Synvaxis te enviará un POST JSON cada vez que ocurra un evento que te interesa.
Eventos disponibles
| Evento | Cuándo se dispara |
|---|---|
| `message.received` | Llega un mensaje entrante (WhatsApp / Messenger) |
| `message.sent` | El bot terminó de enviar una respuesta (un evento por turno) |
| `conversation.handed_off` | Una conversación se escala a humano |
| `order.created` | Se crea una orden (e-commerce) |
| `order.paid` | Una orden se marca como pagada |
| `appointment.scheduled` | Se agenda una cita |
| `appointment.cancelled` | Se cancela una cita |
Forma del payload
{
"event": "order.paid",
"data": {
"order_id": "…",
"external_id": "…",
"platform": "shopify",
"total": 1500,
"customer_phone": "+5215512345678",
"customer_name": "María",
"conversation_id": "…"
},
"timestamp": "2026-04-27T12:34:56.789Z",
"request_id": "8f1a…"
}
Headers que enviamos
X-Synvaxis-Signature: sha256=<hex> # HMAC del body con tu secret
X-Synvaxis-Event: order.paid
X-Synvaxis-Request-Id: 8f1a…
X-Synvaxis-Delivery-Id: 7c2b… # útil para correlacionar con el log
X-Synvaxis-Attempt: 1 # número de intento (1..5)
User-Agent: Synvaxis-Webhook/1.0
Verificación de la firma
Calcula el HMAC-SHA256 del body crudo con tu secret (devuelto al crear el webhook) y compara con el header X-Synvaxis-Signature (sin el prefijo sha256=).
// Node.js / Express
import crypto from 'node:crypto';
app.post('/hooks/synvaxis', express.raw({ type: 'application/json' }), (req, res) => {
const sig = (req.headers['x-synvaxis-signature'] || '').replace(/^sha256=/, '');
const expected = crypto.createHmac('sha256', SYNVAXIS_SECRET).update(req.body).digest('hex');
if (sig.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString());
// ... procesa event.event y event.data ...
res.sendStatus(200);
});
Reintentos
Si tu endpoint responde con un status fuera de 2xx o tarda más de 8 segundos, el dispatcher marca la entrega como fallida y la reencola con backoff exponencial:
| Intento | Espera antes del retry |
|---|---|
| 1 → 2 | 30 segundos |
| 2 → 3 | 2 minutos |
| 3 → 4 | 10 minutos |
| 4 → 5 | 1 hora |
| 5 → final | 6 horas |
Tras 5 fallos consecutivos la entrega se marca failed definitivamente. Tras 20 fallos consecutivos del mismo endpoint, lo marcamos como roto y dejamos de enviar nuevas entregas hasta que actualices la URL.
> Puedes reintentar manualmente cualquier delivery desde el dashboard
> en Perfil → API & Webhooks → Deliveries.
Buenas prácticas #
- Devuelve
200lo antes posible (idealmente <2s) y haz el procesamiento real en background. Synvaxis no necesita esperar a que termines. - Verifica siempre la firma antes de confiar en el body. Sin verificación, cualquiera con tu URL puede inyectar eventos falsos.
- Persiste
request_iden tus logs — soporte usa ese ID para investigar incidencias. - Si reciben el mismo
request_iddos veces, puedes hacer dedup. Stripe-style: el mismo evento puede reentregarse en condiciones de red.