---
title: Cómo configurar webhooks para recibir avisos en tu sistema
updated: 2026-10-07
canonical: https://corekchat.com/ayuda/webhooks
---

Un webhook es un aviso que CorekChat le manda a tu sistema en el momento en
que algo pasa: entra un pedido, cambia de estado, se crea o se cancela una
reserva, se abre o se cierra una mesa. Así tu facturación, tu contabilidad o tu
tablero se enteran solos, sin tener que preguntar cada rato. La primera parte
es para quien administra la cuenta; la segunda, para quien programa el
receptor.

## Paso 1: abre la pestaña Webhooks

Abre **Configuraciones** y elige **Webhooks**, en el grupo **Desarrolladores**. Ahí están las
direcciones que reciben avisos (se llaman *endpoints*) y, abajo, el historial
de las últimas entregas.

(Captura del sistema, sólo para clientes: La sección Webhooks de Configuraciones con un endpoint activo y el historial de entregas)

## Paso 2: crea el endpoint

Pulsa **Nuevo endpoint** y completa:

- **Dirección (HTTPS)**: la que te da quien programa el receptor. Tiene que
  empezar con `https://` y estar publicada en internet; no se aceptan
  direcciones de una red interna.
- **Descripción**: para reconocerlo después («Sistema de facturación»).
- **Eventos**: marca sólo los que ese sistema usa.

Pulsa **Guardar**. Puedes tener hasta diez endpoints.

(Captura del sistema, sólo para clientes: El formulario de nuevo endpoint con la dirección, la descripción y dos eventos marcados)

## Paso 3: guarda el secreto

Al guardar aparece el **secreto** del endpoint, **una sola vez**. Cópialo y
pásaselo a quien programa el receptor: con él comprueba que cada aviso salió
de CorekChat y no de un tercero. Si se pierde, usa **Rotar secreto**: se genera
uno nuevo y el anterior deja de servir apenas termina la rotación, así que hay
que cargar el nuevo en el receptor enseguida.

(Captura del sistema, sólo para clientes: El aviso «Guarde este secreto ahora» con el secreto completo y el botón Copiar)

## Revisar las entregas

En **Últimas entregas** ves cada aviso, a qué endpoint fue, si llegó y qué
respondió el receptor. Una entrega **Entregada** es la que el receptor aceptó;
una **Falló** agotó sus intentos y se puede mandar de nuevo con **Reintentar**.

Si un endpoint falla **cinco entregas seguidas**, se desactiva solo y te
llega un correo: así un receptor caído no se queda acumulando avisos. Cuando
se corrija, pulsa **Reactivar**. **Desactivar** hace lo mismo a mano, por
ejemplo durante un cambio de proveedor.

## Para quien programa: qué llega

Cada aviso es un `POST` a la dirección del endpoint, con un cuerpo JSON así:

```json
{
  "id": "5d0e3c1a-…",
  "event": "order.created",
  "createdAt": "2026-09-27T13:05:12.000Z",
  "data": {
    "orderId": "8f1c…",
    "branchId": "3a2e…",
    "channel": "whatsapp",
    "status": "pending",
    "total": 42000,
    "itemCount": 2,
    "createdAt": "2026-09-27T13:05:12.000Z"
  }
}
```

Y con estos encabezados:

| Encabezado | Qué trae |
|---|---|
| `X-Corek-Event` | El nombre del evento, igual que `event` |
| `X-Corek-Delivery` | El id de la entrega, igual que `id` |
| `X-Corek-Signature` | La firma: `t=<segundos>,v1=<firma>` |

### Los eventos

| Evento | Cuándo | Qué trae en `data` |
|---|---|---|
| `order.created` | Entra un pedido | `orderId`, `branchId`, `channel`, `status`, `total`, `itemCount`, `createdAt` |
| `order.status_changed` | Un pedido cambia de estado | `orderId`, `branchId`, `previousStatus`, `status`, `total`, `changedAt` |
| `reservation.created` | Se crea una reserva | `reservationId`, `branchId`, `status`, `partySize`, `scheduledAt`, `createdAt` |
| `reservation.cancelled` | Se cancela una reserva | `reservationId`, `branchId`, `partySize`, `scheduledAt`, `cancelledBy` (`customer` o `staff`), `cancelledAt` |
| `conversation.escalated` | El asistente pasa una conversación a una persona | `conversationId`, `channel`, `reason`, `escalatedAt` |
| `table.session_opened` | Se abre una mesa en el salón | `sessionId`, `tableId`, `tableLabel`, `branchId`, `partySize`, `openedAt` |
| `table.session_closed` | Se cierra una mesa | `sessionId`, `tableId`, `tableLabel`, `branchId`, `openedAt`, `closedAt`, `total`, `orderCount` |
| `order.updated` | Tu equipo modifica un pedido desde el panel (productos, cantidades, dirección, pago, nota o datos del cliente) | `orderId`, `branchId`, `status`, `total`, `itemCount`, `changes`, `updatedAt` |

Los avisos llevan lo mínimo a propósito. Para el detalle de un pedido o de una
reserva, consulta la [API](/ayuda/api-publica) con el id que llegó. Los dos
eventos de mesas sólo salen con el módulo POS.

`changes` de `order.updated` es la lista de lo que cambió, con estos nombres:
`items`, `total`, `deliveryAddress`, `paymentMethod`, `notes`, `customerName`,
`customerPhone` y `deliveryFee` (el valor del domicilio, cuando el cliente
cambia la dirección de un pedido pendiente por el chat). Si un pedido vuelve a un estado anterior (por ejemplo, de
«Enviado» a «En preparación»), lo que llega es `order.status_changed`, con el
estado anterior en `previousStatus`.

### Verificar la firma

La firma es un HMAC-SHA256, con el secreto del endpoint, sobre el texto
`<t>.<cuerpo>`: los segundos de `t`, un punto y el cuerpo **exactamente como
llegó**, antes de convertirlo a JSON. Rechaza el aviso si la firma no
coincide o si `t` tiene más de cinco minutos: así nadie puede reenviar un aviso
viejo que haya capturado.

```js
// Node.js — ejemplo con Express. Ojo: hay que leer el cuerpo CRUDO.
const crypto = require('crypto');
const express = require('express');
const app = express();

// El whsec_… del paso 3.
const SECRETO = process.env.COREK_WEBHOOK_SECRET;

const crudo = express.raw({ type: 'application/json' });

app.post('/webhooks/corekchat', crudo, (req, res) => {
  const cuerpo = req.body.toString('utf8');
  const encabezado = String(req.get('X-Corek-Signature') || '');
  const partes = Object.fromEntries(
    encabezado.split(',').map((p) => p.split('=')),
  );
  const t = Number(partes.t);
  const esperada = crypto
    .createHmac('sha256', SECRETO)
    .update(`${t}.${cuerpo}`)
    .digest('hex');

  const firmaOk =
    partes.v1 &&
    partes.v1.length === esperada.length &&
    crypto.timingSafeEqual(
      Buffer.from(partes.v1),
      Buffer.from(esperada),
    );
  const reciente = Math.abs(Date.now() / 1000 - t) <= 300;
  if (!firmaOk || !reciente) return res.status(400).end();

  const aviso = JSON.parse(cuerpo);
  // …procesa aviso.event y aviso.data…
  res.status(200).end();
});
```

```python
# Python — la misma cuenta.
import hashlib, hmac, time

def firma_valida(secreto: str, encabezado: str, cuerpo: bytes) -> bool:
    partes = dict(p.split("=", 1) for p in encabezado.split(",") if "=" in p)
    t = int(partes.get("t", "0"))
    firmado = f"{t}.".encode() + cuerpo
    esperada = hmac.new(secreto.encode(), firmado, hashlib.sha256).hexdigest()
    reciente = abs(time.time() - t) <= 300
    return hmac.compare_digest(partes.get("v1", ""), esperada) and reciente
```

### Cómo responder y qué pasa si falla

- Responde con un código **2xx** en menos de **10 segundos**. Si el trabajo es
  largo, guarda el aviso, responde y procésalo después.
- Si el receptor no contesta o responde un error del servidor (5xx), el aviso
  se reintenta solo: hasta **cinco intentos**, a los 30 segundos, 2 minutos,
  8 minutos y 32 minutos.
- Un error del cliente (4xx) **no** se reintenta, porque repetir lo mismo daría
  el mismo error; salvo 408 y 429, que sí.
- Un mismo aviso puede llegar más de una vez. Los reintentos automáticos
  conservan el mismo `id`; un **Reintentar** manual crea una entrega nueva con
  otro `id`. Para no procesar dos veces el mismo hecho, guíate por el dato del
  evento: por ejemplo, el `orderId` junto con el `status`.
