Instala el chat web en minutos.
Obtén la clave pública en Configuración → Canales → Chat web. Registra previamente los dominios permitidos y pega el snippet antes de cerrar </body>.
<script
src="https://vertichat.verticali.mx/widget.js"
data-nexo-key="TU_PUBLIC_KEY"
data-color="#3157FF"
async>
</script>No concede acceso al workspace. Las sesiones solo se emiten para orígenes autorizados y tienen sus propios límites.
Un mecanismo para cada superficie.
Clave pública
Identifica el canal web. Se combina con la lista de orígenes permitidos.
data-nexo-keyBearer de widget
Autoriza mensajes, lectura y cargas de un visitante. No lo compartas entre usuarios.
Authorization: Bearer …Firma webhook
Autentica el cuerpo crudo de cada evento saliente mediante HMAC-SHA256.
X-SipoChat-SignatureSesión de aplicación
Las rutas internas del CRM y la bandeja requieren cookie y mismo origen; no son una API pública.
SameSite + OriginNo automatices las rutas internas /api/app, /api/crm o /api/outbound-webhooks desde un servidor externo. Hoy se administran desde la interfaz autenticada.
Crea una sesión y conversa.
El widget oficial ya implementa este circuito. Usa la API directamente solo si necesitas construir una interfaz propia.
1. Crear sesión
curl -X POST \
'https://vertichat.verticali.mx/api/widget/TU_PUBLIC_KEY/session' \
-H 'Origin: https://tuempresa.com' \
-H 'Content-Type: application/json' \
-d '{
"anonymousId": "visitor_123",
"pageUrl": "https://tuempresa.com/precios"
}'{
"data": {
"token": "widget_session_token",
"anonymousId": "visitor_123",
"expiresAt": 1787328000000,
"channel": {
"name": "Ventas web",
"greeting": "¡Hola! ¿En qué podemos ayudarte?",
"color": "#3157FF"
}
}
}2. Enviar texto
const response = await fetch(
"https://vertichat.verticali.mx/api/widget/TU_PUBLIC_KEY/messages",
{
method: "POST",
headers: {
"Authorization": `Bearer ${session.token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
content: "Quiero una demostración para mi equipo"
})
}
);
if (!response.ok) throw await response.json();
const { data } = await response.json();3. Enviar un archivo
const form = new FormData();
form.append("file", selectedFile);
form.append("caption", "Documento para la cotización");
const response = await fetch(
"https://vertichat.verticali.mx/api/widget/TU_PUBLIC_KEY/uploads",
{
method: "POST",
headers: { "Authorization": `Bearer ${session.token}` },
body: form
}
);| Método | Ruta | Autorización | Uso |
|---|---|---|---|
| GET | /api/widget/{publicKey}/bootstrap | Pública | Configuración visible del canal |
| POST | /api/widget/{publicKey}/session | Origen permitido | Crea una sesión de visitante |
| GET | /api/widget/{publicKey}/messages?after={cursor} | Bearer widget | Lista mensajes de la sesión |
| POST | /api/widget/{publicKey}/messages | Bearer widget | Envía un mensaje de texto |
| POST | /api/widget/{publicKey}/uploads | Bearer widget | Envía un archivo multipart |
| POST | /api/widget/{publicKey}/read | Bearer widget | Marca la conversación como leída |
| GET | /api/public/webhook-attachments/{id}?token=… | URL firmada | Descarga temporal de un adjunto |
Las cargas requieren Content-Length, usan multipart/form-data y aplican límites por sesión, visitante y canal.
Recibe eventos de chat y CRM.
En Vertichat abre Webhooks → Nuevo webhook, registra una URL HTTPS, selecciona eventos y copia el secreto. El secreto solo se muestra al crear o rotar.
Cabeceras de entrega
X-SipoChat-EventX-SipoChat-DeliveryX-SipoChat-TimestampX-SipoChat-AttemptX-SipoChat-SignatureX-SipoChat-Spec-Version: 1.0Payload Vertichat v1
{
"specversion": "1.0",
"id": "evt_8f9c...",
"type": "deal.stage_changed",
"version": "2026-07-01",
"source": "https://vertichat.verticali.mx",
"workspace_id": "wsp_123",
"subject": "deal/deal_456",
"time": "2026-07-21T16:10:00.000Z",
"actor": { "type": "user", "id": "usr_789" },
"context": { "conversation_id": "cnv_234" },
"data": { "id": "deal_456", "title": "Plan Empresa" },
"changes": {
"stage_id": { "previous": "stage_new", "current": "stage_demo" }
},
"private": false
}Verificar la firma
import crypto from "node:crypto";
export function verifySipoChatWebhook(rawBody, headers, secret) {
const timestamp = headers["x-sipochat-timestamp"];
const received = headers["x-sipochat-signature"];
if (!timestamp || !received?.startsWith("v1=")) return false;
const ageMs = Math.abs(Date.now() - Number(timestamp) * 1000);
if (ageMs > 5 * 60 * 1000) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + "." + rawBody)
.digest("hex");
const actual = received.slice(3);
if (actual.length !== expected.length) return false;
return crypto.timingSafeEqual(
Buffer.from(actual, "hex"),
Buffer.from(expected, "hex")
);
}La entrega es al menos una vez. Vertichat reintenta fallos transitorios con backoff; tu consumidor debe ser idempotente.
Suscríbete solo a lo que necesitas.
Las notas internas se excluyen por defecto. Para recibir note.created, un administrador debe activar explícitamente la inclusión de eventos privados.
Conversaciones
message.creatednote.createdattachment.createdconversation.createdconversation.updatedconversation.status_changedContactos
contact.createdcontact.updatedcompany.createdcompany.updatedCRM
deal.createddeal.updateddeal.stage_changeddeal.wondeal.lostactivity.createdTareas
task.createdtask.updatedtask.completedtask.cancelledtask.reopenedCotizaciones
quote.createdquote.sentquote.acceptedquote.rejectedOperación
webhook.testSelecciona chatwoot_compat_v1. Vertichat convierte el nombre del evento y los atributos modificados a una estructura compatible para facilitar la transición.
Callbacks que configura Vertichat.
Estas rutas reciben mensajes desde los proveedores. No son endpoints para emitir mensajes desde tu integración; el asistente de Canales las configura y protege.
WhatsApp Cloud API
GET/POST /api/webhooks/whatsappGET valida el verify token. POST exige X-Hub-Signature-256 de Meta.
Vertichat Connect
POST /api/webhooks/evolution/{channelId}Ruta interna del conector. Exige X-Nexo-Evolution-Secret, valida la instancia y responde 202 al encolar.
Integra pensando en fallos.
{
"error": {
"code": "widget_session_rate_limited",
"message": "Se iniciaron demasiadas sesiones para este visitante.",
"details": { "retryAfterSeconds": 60 }
}
}