---
title: How to set up webhooks to receive notifications in your system
updated: 2026-10-07
canonical: https://corekchat.com/ayuda/en/webhooks
---

A webhook is a notification CorekChat sends to your system the moment something
happens: an order comes in, its status changes, a reservation is created or
cancelled, a table is opened or closed. That way your invoicing, your accounting
or your dashboard find out on their own, without having to ask every so often.
The first part is for whoever manages the account; the second, for whoever
programs the receiver.

## Step 1: open the Webhooks tab

Open «Configuraciones» (Settings) and choose «Webhooks», in the
«Desarrolladores» (Developers) group. There you find the addresses that receive
notifications (they're called *endpoints*) and, below, the history of the latest
deliveries.

(Captura del sistema, sólo para clientes: The Webhooks section of Settings with an active endpoint and the delivery history)

## Step 2: create the endpoint

Press «Nuevo endpoint» (New endpoint) and fill in:

- **Address (HTTPS)**: the one given to you by whoever programs the receiver. It
  has to start with `https://` and be published on the internet; addresses on an
  internal network aren't accepted.
- **Description**: to recognize it later ("Invoicing system").
- **Events**: check only the ones that system uses.

Press «Guardar» (Save). You can have up to ten endpoints.

(Captura del sistema, sólo para clientes: The new endpoint form with the address, the description and two events checked)

## Step 3: save the secret

When you save, the endpoint's **secret** appears, **only once**. Copy it and
hand it to whoever programs the receiver: with it they check that each
notification came from CorekChat and not from a third party. If it's lost, use
«Rotar secreto» (Rotate secret): a new one is generated and the previous one
stops working as soon as the rotation finishes, so the new one has to be loaded
into the receiver right away.

(Captura del sistema, sólo para clientes: The Save this secret now notice with the full secret and the Copy button)

## Reviewing deliveries

Under «Últimas entregas» (Latest deliveries) you see each notification, which
endpoint it went to, whether it arrived and what the receiver replied. A
«Entregada» (Delivered) delivery is one the receiver accepted; a «Falló»
(Failed) one ran out of attempts and can be sent again with «Reintentar»
(Retry).

If an endpoint fails **five deliveries in a row**, it deactivates itself and you
get an email: that way a receiver that's down doesn't keep piling up
notifications. Once it's fixed, press «Reactivar» (Reactivate). «Desactivar»
(Deactivate) does the same by hand, for example during a provider change.

## For whoever programs it: what arrives

Each notification is a `POST` to the endpoint's address, with a JSON body like
this:

```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"
  }
}
```

And with these headers:

| Header | What it carries |
|---|---|
| `X-Corek-Event` | The event name, same as `event` |
| `X-Corek-Delivery` | The delivery id, same as `id` |
| `X-Corek-Signature` | The signature: `t=<seconds>,v1=<signature>` |

### The events

| Event | When | What it carries in `data` |
|---|---|---|
| `order.created` | An order comes in | `orderId`, `branchId`, `channel`, `status`, `total`, `itemCount`, `createdAt` |
| `order.status_changed` | An order changes status | `orderId`, `branchId`, `previousStatus`, `status`, `total`, `changedAt` |
| `reservation.created` | A reservation is created | `reservationId`, `branchId`, `status`, `partySize`, `scheduledAt`, `createdAt` |
| `reservation.cancelled` | A reservation is cancelled | `reservationId`, `branchId`, `partySize`, `scheduledAt`, `cancelledBy` (`customer` or `staff`), `cancelledAt` |
| `conversation.escalated` | The assistant hands a conversation to a person | `conversationId`, `channel`, `reason`, `escalatedAt` |
| `table.session_opened` | A table is opened in the dining room | `sessionId`, `tableId`, `tableLabel`, `branchId`, `partySize`, `openedAt` |
| `table.session_closed` | A table is closed | `sessionId`, `tableId`, `tableLabel`, `branchId`, `openedAt`, `closedAt`, `total`, `orderCount` |
| `order.updated` | Your team edits an order from the dashboard (products, quantities, address, payment, note or customer details) | `orderId`, `branchId`, `status`, `total`, `itemCount`, `changes`, `updatedAt` |

Notifications carry the minimum on purpose. For an order's or reservation's
details, query the [API](/ayuda/en/api-publica) with the id you received. The
two table events are only sent with the POS module.

The `changes` field of `order.updated` is the list of what changed, with these
names: `items`, `total`, `deliveryAddress`, `paymentMethod`, `notes`,
`customerName`, `customerPhone` and `deliveryFee` (the delivery fee, when the
customer changes the address of a pending order through the chat). If an order
goes back to an earlier status (for example, from "Sent" to "Preparing"), what
arrives is `order.status_changed`, with the previous status in
`previousStatus`.

### Verifying the signature

The signature is an HMAC-SHA256, using the endpoint's secret, over the text
`<t>.<body>`: the seconds in `t`, a dot and the body **exactly as it arrived**,
before parsing it as JSON. Reject the notification if the signature doesn't
match or if `t` is more than five minutes old: that way nobody can resend an old
notification they captured.

```js
// Node.js — example with Express. Careful: you have to read the RAW body.
const crypto = require('crypto');
const express = require('express');
const app = express();

// The whsec_… from step 3.
const SECRET = process.env.COREK_WEBHOOK_SECRET;

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

app.post('/webhooks/corekchat', raw, (req, res) => {
  const body = req.body.toString('utf8');
  const header = String(req.get('X-Corek-Signature') || '');
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=')),
  );
  const t = Number(parts.t);
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(`${t}.${body}`)
    .digest('hex');

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

  const notification = JSON.parse(body);
  // …process notification.event and notification.data…
  res.status(200).end();
});
```

```python
# Python — the same calculation.
import hashlib, hmac, time

def valid_signature(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t = int(parts.get("t", "0"))
    signed = f"{t}.".encode() + body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    recent = abs(time.time() - t) <= 300
    return hmac.compare_digest(parts.get("v1", ""), expected) and recent
```

### How to respond and what happens if it fails

- Respond with a **2xx** code in under **10 seconds**. If the work takes long,
  store the notification, respond and process it afterwards.
- If the receiver doesn't answer or responds with a server error (5xx), the
  notification is retried automatically: up to **five attempts**, after 30
  seconds, 2 minutes, 8 minutes and 32 minutes.
- A client error (4xx) is **not** retried, because repeating the same thing
  would give the same error; except 408 and 429, which are.
- The same notification can arrive more than once. Automatic retries keep the
  same `id`; a manual «Reintentar» (Retry) creates a new delivery with a
  different `id`. To avoid processing the same event twice, go by the event's
  data: for example, the `orderId` together with the `status`.

