> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.userbot.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.userbot.ai/_mcp/server.

# Ricevere eventi webhook

Userbot invia eventi al tuo server tramite **HTTP POST** quando accadono cose rilevanti: un nuovo messaggio, una conversazione avviata, un cambio di stato, un aggiornamento variabili. Non serve fare polling — il tuo backend riceve una push ogni volta che qualcosa cambia.

## Perché i webhook sono obbligatori

Le integrazioni Userbot **non** devono costruire la UI chat dalla risposta REST di `POST /v1/bots/{bot_key}/messages/`. L'agente può generare più messaggi per ogni input; dopo un'escalation i messaggi dell'operatore devono comparire in tempo reale. Tutto questo arriva sul webhook registrato in dashboard — in particolare tramite l'evento **`message.added`**.

In produzione:

1. Registra un endpoint HTTPS in **dashboard Userbot**
2. Invia messaggi REST con **`webhookOnly: true`**
3. Aggiorna la UI **solo** dagli eventi webhook verificati (HMAC)

Il parametro `webhookUrl` nel body REST esiste **solo per test locali** — non sostituisce il webhook in dashboard.

Per il flusso completo passo passo, vedi [Quickstart](/quickstart) e [Concetti chiave](/concetti-chiave).

Per ricevere gli eventi devi esporre un endpoint **HTTPS** raggiungibile pubblicamente da internet. Userbot non consegna webhook a `localhost` o URL HTTP non sicuri.

## Setup

La configurazione richiede tre passi:

1. **Crea un endpoint** sul tuo server che accetti POST JSON e risponda 200 entro 5 secondi
2. **Registra l'URL** nella dashboard Userbot — Userbot ti fornirà un **Webhook Secret** per la verifica delle firme (mostrato **una sola volta** in creazione, salvalo subito)
3. **Implementa la verifica HMAC** — ogni richiesta va validata prima di processare il payload ([Verifica firma HMAC](/api-verifica-hmac))

Il tuo endpoint deve rispondere con status **200** entro **5 secondi**. Se rispondi in ritardo o con errore, Userbot ritenta la consegna — e potresti ricevere lo stesso evento più volte. Per questo serve la deduplicazione per `eventId`.

## Headers della richiesta

Ogni webhook include header che identificano l'evento e permettono la verifica della firma:

| Header                | Descrizione                                                                |
| --------------------- | -------------------------------------------------------------------------- |
| `x-userbot-signature` | Firma HMAC-SHA256 del payload (hex) — confrontala con il calcolo locale    |
| `x-userbot-timestamp` | Timestamp Unix in secondi — verifica che sia entro ±5 minuti (anti-replay) |
| `x-userbot-event-id`  | ID univoco dell'evento — usa per deduplicazione                            |
| `x-userbot-attempt`   | Numero del tentativo (1, 2, 3…) — utile per capire se è un retry           |

## Struttura del body

Tutti gli eventi condividono una struttura comune. Il campo `data` varia in base al tipo di evento:

```json
{
  "event": "message.added",
  "eventId": "evt_abc123",
  "sessionId": 48291,
  "createdAt": 1715500800,
  "data": { }
}
```

| Campo       | Tipo   | Descrizione                                                          |
| ----------- | ------ | -------------------------------------------------------------------- |
| `event`     | string | Tipo di evento — determina la struttura di `data`                    |
| `eventId`   | string | Identificativo univoco dell'evento — obbligatorio per deduplicazione |
| `sessionId` | number | ID numerico della sessione (opzionale per `user.variables.created`)  |
| `createdAt` | number | Timestamp Unix di creazione dell'evento                              |
| `data`      | object | Payload specifico dell'evento — messaggio, stato, variabili, ecc.    |

Per tipi di evento, campi in `data` ed esempi di payload per ciascun evento, consulta [Userbot Webhook Events](/api-reference/userbot-webhook-events) nella tab API Reference.

I valori di `state` negli eventi webhook (`agent_assigned`, `bot_assigned`) differiscono dalla nomenclatura usata nelle API REST di aggiornamento stato (`escalated`, `bot`). Tieni conto del mapping nella tua integrazione — vedi [Concetti chiave](/concetti-chiave).

## Esempio server

Gli esempi sotto mostrano un handler completo: verifica HMAC, controllo timestamp, deduplicazione implicita (log per evento) e risposta 200. Adattali al tuo framework e al tuo secret manager.

#### Node.js (Express)

```javascript
const express = require("express");
const crypto = require("crypto");

const app = express();
const WEBHOOK_SECRET = "il_tuo_webhook_secret";

app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; },
}));

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-userbot-signature"];
  const timestamp = req.headers["x-userbot-timestamp"];

  if (!signature || !timestamp) {
    return res.status(401).json({ error: "Missing signature headers" });
  }

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
    return res.status(401).json({ error: "Timestamp expired" });
  }

  const rawBody = req.rawBody.toString("utf8");
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const sigBuf = Buffer.from(signature, "hex");
  const expectedBuf = Buffer.from(expected, "hex");
  if (sigBuf.length !== expectedBuf.length ||
      !crypto.timingSafeEqual(sigBuf, expectedBuf)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, sessionId, data } = req.body;
  switch (event) {
    case "message.added":
      console.log(`Nuovo messaggio in sessione ${sessionId}:`, data);
      break;
    case "conversation.created":
      console.log(`Nuova conversazione: ${sessionId}`);
      break;
    case "conversation.state_changed":
      console.log(`Stato cambiato: ${sessionId} → ${data.state}`);
      break;
    case "conversation.archived":
      console.log(`Conversazione archiviata: ${sessionId}`);
      break;
    case "user.variables.updated":
      console.log(`Variabili aggiornate per sessione ${sessionId}:`, data);
      break;
    default:
      console.log(`Evento sconosciuto: ${event}`);
  }

  res.status(200).json({ status: "ok" });
});

app.listen(3000);
```

#### Python (Flask)

```python
import hmac
import hashlib
import time
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "il_tuo_webhook_secret"

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("x-userbot-signature")
    timestamp = request.headers.get("x-userbot-timestamp")

    if not signature or not timestamp:
        return jsonify({"error": "Missing signature headers"}), 401

    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return jsonify({"error": "Timestamp expired"}), 401

    raw_body = request.get_data(as_text=True)
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return jsonify({"error": "Invalid signature"}), 401

    payload = request.get_json()
    event = payload.get("event")
    session_id = payload.get("sessionId")
    data = payload.get("data", {})

    if event == "message.added":
        print(f"Nuovo messaggio in sessione {session_id}: {data}")
    elif event == "conversation.created":
        print(f"Nuova conversazione: {session_id}")
    elif event == "conversation.state_changed":
        print(f"Stato sessione {session_id}: {data.get('state')}")
    elif event == "conversation.archived":
        print(f"Conversazione archiviata: {session_id}")

    return jsonify({"status": "ok"}), 200
```

Per un tutorial end-to-end che collega REST, webhook e HMAC, vedi [Tutorial: prima integrazione](/tutorial-prima-integrazione).