Blu Pantheon
Automation & RPA

Webhook

Contratto completo dei webhook Blu Pantheon: registrazione, header, envelope, catalogo eventi, ritentativi, dead letter e test locale.

I webhook sono il canale outbound della piattaforma: quando accade un evento di business, Blu Pantheon esegue una POST HTTPS firmata verso l'endpoint che hai registrato.

Prima di iniziare

  • Un endpoint HTTPS pubblico con certificato TLS valido
  • Permesso identity:webhooks:write sul tenant
  • Un archivio per la deduplica delle consegne (tabella o cache con TTL di 7 giorni)

Requisiti dell'endpoint

RequisitoDettaglioSe non rispettato
ProtocolloSolo https://. HTTP in chiaro è rifiutato in fase di registrazione.Registrazione bloccata
Certificato TLSValido, non scaduto, catena completa, hostname corrispondente.Consegna fallita, nessun ritentativo utile
RaggiungibilitàPubblica da Internet. Nessuna VPN, nessuna autenticazione di rete.Timeout di connessione
MetodoDeve accettare POST con Content-Type: application/json.405 e consegna fallita
RispostaUn qualsiasi 2xx entro 10 secondi. Consigliato 202.Ritentativo
RedirectNessuno. 301, 302, 307, 308 non vengono seguiti.Consegna fallita
Dimensione rispostaCorpo di risposta ignorato, mantienilo vuoto o minimo.Nessun impatto
Autenticazione applicativaNon usare Basic Auth o header custom: l'autenticità si verifica con la firma HMAC.401 sul tuo endpoint

Perché rispondere 202

202 Accepted comunica correttamente la semantica: hai preso in carico il messaggio, lo elaborerai in modo asincrono. Consente di rispondere in pochi millisecondi anche quando il sistema a valle è lento.

Registrare un endpoint

ConsoleConfigurazioneWebhookNuovo webhook

Apri la sezione Webhook

Dal menu Configurazione seleziona Webhook. La tabella mostra gli endpoint registrati con stato, eventi sottoscritti e percentuale di consegne riuscite nelle ultime 24 ore.

Inserisci i dati dell'endpoint

CampoValore
NomeAutomazione ACME — produzione
URLhttps://automation.acme.it/webhooks/blupantheon
Progettobp_proj_7f3a9c21
StatoAttivo
Versione API2026-09-01

Seleziona gli eventi

Sottoscrivi solo gli eventi che il tuo flusso gestisce. Una sottoscrizione totale genera traffico inutile e rende difficile la diagnosi.

Copia il segreto di firma

Alla creazione viene generato il segreto. Copialo subito: non sarà più visibile.

Segreto di firma

whsec_xx••••••••••••••

Usato per calcolare e verificare X-BP-Signature. Conservalo nel gestore di segreti.

Invia un evento di prova

Usa il pulsante Invia evento di prova e scegli webhook.test. Verifica nei log consegne che la risposta sia 2xx.

Header di ogni consegna

Header HTTP inviati da Blu Pantheon

ParametroTipoObblig.DefaultDescrizione
X-BP-SignaturestringSì—Firma HMAC-SHA256 nel formato "t=1767225600,v1=<hex>". Può contenere più valori v1 durante una rotazione.
X-BP-EventstringSì—Tipo di evento, es. "session.handover.requested". Coincide con il campo event del payload.
X-BP-Deliverystring (uuid)Sì—Identificativo univoco della consegna. Costante tra i ritentativi. Usalo per la deduplica.
X-BP-Timestampstring (unix seconds)Sì—Istante della firma in secondi Unix. Deve rientrare nella tolleranza di 5 minuti.
X-BP-Attemptstring (numero)Sì—Numero del tentativo, da 1 a 5. Utile per distinguere una prima consegna da un ritentativo.
X-BP-Api-VersionstringSì—Versione del contratto del payload, es. "2026-09-01".
Content-TypestringSì—Sempre "application/json; charset=utf-8".
User-AgentstringSì—Sempre "BluPantheon-Webhooks/1.0". Non usarlo come meccanismo di autenticazione.

Envelope comune

Tutti i payload condividono la stessa struttura esterna. I dati specifici dell'evento stanno in data.

Struttura comune
{
  "id": "evt_01J9X4K2R7M3Q8YV5T0B6D2WQ",
  "event": "session.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:20:00.412Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {}
}
CampoTipoDescrizione
idstringIdentificativo dell'evento. Stabile tra i ritentativi.
eventstringTipo di evento, coincide con X-BP-Event.
apiVersionstringVersione del contratto del payload.
createdAtstring ISO 8601 UTCIstante in cui l'evento è stato generato. Usalo per l'ordinamento.
tenantIdstringTenant proprietario dei dati.
projectIdstringProgetto di origine dell'evento.
dataobjectContenuto specifico dell'evento.

Catalogo eventi

Sessioni

EventoSi attiva quandoUso tipico
session.createdViene aperta una nuova conversazioneTracciamento, apertura pratica
session.message.createdViene aggiunto un messaggio alla sessioneArchiviazione trascrizioni
session.taggedViene applicata un'etichetta alla sessioneInstradamento per categoria
session.ratedIl visitatore lascia una valutazioneIndicatori di qualità
session.closedLa conversazione viene chiusaChiusura pratica, riepilogo
session.expiredLa sessione scade per inattivitàPulizia, riconciliazione

Handover

EventoSi attiva quandoUso tipico
session.handover.requestedServe un operatore umanoCreazione ticket, notifica al team
session.handover.assignedUn operatore prende in caricoAggiornamento stato ticket
session.handover.completedL'operatore restituisce la sessione all'agenteChiusura escalation
session.handover.timeoutNessun operatore disponibile entro la sogliaEscalation di secondo livello

Clienti

EventoSi attiva quandoUso tipico
client.createdViene creata un'anagrafica clienteCreazione lead nel CRM
client.updatedCambiano i dati anagrafici o di contattoAllineamento CRM
client.mergedDue anagrafiche vengono unificateDeduplica nel gestionale
client.consent.updatedCambia un consenso privacyAggiornamento registro trattamenti
client.deletedL'anagrafica viene cancellataCancellazione a valle per GDPR

Prenotazioni

EventoSi attiva quandoUso tipico
booking.createdViene creata una prenotazioneBlocco risorsa nell'ERP
booking.confirmedLa prenotazione viene confermataScrittura definitiva a gestionale
booking.rescheduledCambia data o orarioAggiornamento agenda
booking.cancelledLa prenotazione viene annullataLiberazione risorsa
booking.reminder.dueÈ il momento del promemoriaInvio SMS o email dal tuo sistema
booking.no_showIl cliente non si presentaStatistiche, riaddebito

Documenti

EventoSi attiva quandoUso tipico
document.uploadedUn documento viene caricatoPresa in carico
document.classifiedLa classificazione automatica è completataDeposito in DMS o protocollo
document.extraction.completedL'estrazione dati è terminataImport nel gestionale
document.rejectedIl documento è illeggibile o non conformeRichiesta di nuovo invio

Fatturazione

EventoSi attiva quandoUso tipico
invoice.issuedViene emessa una fatturaRegistrazione contabile
invoice.payment.succeededIl pagamento va a buon fineRiconciliazione partitario
invoice.payment.failedIl pagamento fallisceSollecito, blocco servizio
subscription.updatedCambia il piano di abbonamentoAggiornamento contratto

Sistema

EventoSi attiva quandoUso tipico
webhook.testInvii un evento di prova dalla ConsoleVerifica connettività e firma
apikey.rotatedUna chiave API viene ruotataAllerta al team di automazione
webhook.delivery.failedUna consegna esaurisce i ritentativiAllarme operativo

Payload di esempio

session.created

Quando scatta: viene aperta una nuova conversazione su qualsiasi canale

{
  "id": "evt_01J9X4K2R7M3Q8YV5T0B6D2WQ",
  "event": "session.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:20:00.412Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {
    "sessionId": "bp_sess_3d81f0aa",
    "channel": "web-widget",
    "locale": "it-IT",
    "startedAt": "2026-09-04T09:19:58.007Z",
    "agent": {
      "id": "bp_agent_1c4d",
      "name": "Assistente Clienti"
    },
    "visitor": {
      "anonymousId": "vis_9a2f4c77",
      "clientId": null,
      "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
      "referrer": "https://www.acme.it/assistenza"
    },
    "metadata": {
      "campagna": "assistenza-post-vendita"
    },
    "traceId": "trc_6b19e0d4c8f24a7f"
  }
}
session.handover.requested

Quando scatta: il visitatore chiede un operatore o l agente rileva un intento critico

{
  "id": "evt_01J9X5B0PZQ4T1N7C3H8M2AKE",
  "event": "session.handover.requested",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:26:31.884Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {
    "sessionId": "bp_sess_3d81f0aa",
    "reason": "richiesta_esplicita",
    "priority": "alta",
    "queue": "assistenza-tecnica",
    "requestedAt": "2026-09-04T09:26:31.512Z",
    "client": {
      "id": "bp_cli_82ae4f10",
      "displayName": "Mario Rossi",
      "email": "mario.rossi@example.it",
      "phone": "+39 02 1234567"
    },
    "summary": "Il cliente segnala un malfunzionamento del contatore dopo la sostituzione del 2 settembre. Chiede intervento tecnico entro 48 ore.",
    "lastMessages": [
      {
        "role": "visitor",
        "text": "Il contatore continua a segnalare errore E12.",
        "sentAt": "2026-09-04T09:25:40.101Z"
      },
      {
        "role": "agent",
        "text": "Verifico subito. Preferisce parlare con un tecnico?",
        "sentAt": "2026-09-04T09:25:52.664Z"
      },
      {
        "role": "visitor",
        "text": "Sì, per favore.",
        "sentAt": "2026-09-04T09:26:28.930Z"
      }
    ],
    "traceId": "trc_6b19e0d4c8f24a7f"
  }
}
session.closed

Quando scatta: la conversazione viene chiusa dall operatore, dal visitatore o per timeout

{
  "id": "evt_01J9X7C4WK5R2D9F0G6J1LTYB",
  "event": "session.closed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:58:12.220Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {
    "sessionId": "bp_sess_3d81f0aa",
    "closedBy": "operator",
    "operatorId": "bp_usr_5e73b1",
    "closedAt": "2026-09-04T09:58:11.004Z",
    "durationSeconds": 2293,
    "messageCount": 24,
    "outcome": "risolto",
    "tags": ["assistenza-tecnica", "contatore", "intervento-programmato"],
    "rating": {
      "score": 5,
      "comment": "Molto rapido, grazie."
    },
    "summary": "Aperto intervento tecnico PRT-2026-004512 per errore E12 sul contatore. Appuntamento fissato per il 6 settembre.",
    "clientId": "bp_cli_82ae4f10",
    "traceId": "trc_6b19e0d4c8f24a7f"
  }
}
client.created

Quando scatta: viene creata una nuova anagrafica cliente da conversazione, form o API

{
  "id": "evt_01J9X8D9AN6S3E0H2K7P4MVZC",
  "event": "client.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:04:45.771Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {
    "clientId": "bp_cli_82ae4f10",
    "source": "conversazione",
    "sourceSessionId": "bp_sess_3d81f0aa",
    "type": "azienda",
    "displayName": "ACME Impianti S.r.l.",
    "legalName": "ACME Impianti S.r.l.",
    "vatNumber": "IT01234567890",
    "taxCode": "01234567890",
    "sdiCode": "SUBM70N",
    "pec": "acme.impianti@pec.it",
    "email": "amministrazione@acme.it",
    "phone": "+39 02 1234567",
    "address": {
      "street": "Via Torino 42",
      "city": "Milano",
      "province": "MI",
      "postalCode": "20123",
      "country": "IT"
    },
    "consents": {
      "marketing": false,
      "profilazione": false,
      "updatedAt": "2026-09-04T10:04:45.500Z"
    },
    "traceId": "trc_1f28a94b7c0d4e63"
  }
}
booking.confirmed

Quando scatta: una prenotazione passa allo stato confermato

{
  "id": "evt_01J9XA1F2P7T4G1J3M8R5NXQD",
  "event": "booking.confirmed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:31:09.318Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {
    "bookingId": "bp_bkg_5c907e33",
    "reference": "PRE-2026-001874",
    "status": "confirmed",
    "confirmedAt": "2026-09-04T10:31:08.902Z",
    "service": {
      "id": "bp_svc_2a11",
      "name": "Intervento tecnico a domicilio",
      "durationMinutes": 90
    },
    "resource": {
      "id": "bp_res_7b30",
      "name": "Squadra Nord",
      "type": "team"
    },
    "startAt": "2026-09-06T08:30:00+02:00",
    "endAt": "2026-09-06T10:00:00+02:00",
    "timezone": "Europe/Rome",
    "client": {
      "id": "bp_cli_82ae4f10",
      "displayName": "ACME Impianti S.r.l.",
      "email": "amministrazione@acme.it",
      "phone": "+39 02 1234567"
    },
    "location": {
      "type": "onsite",
      "address": "Via Torino 42, 20123 Milano MI"
    },
    "customFields": {
      "matricola_contatore": "CNT-99120384",
      "codice_impianto": "IMP-4471"
    },
    "price": {
      "amount": 12000,
      "currency": "EUR",
      "vatRate": 22
    },
    "sourceSessionId": "bp_sess_3d81f0aa",
    "traceId": "trc_1f28a94b7c0d4e63"
  }
}
document.classified

Quando scatta: la classificazione automatica di un documento caricato è completata

{
  "id": "evt_01J9XB6H8Q9V5K2L4N0S7PWYE",
  "event": "document.classified",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T11:02:53.640Z",
  "tenantId": "bp_ten_4a91c0e5",
  "projectId": "bp_proj_7f3a9c21",
  "data": {
    "documentId": "bp_doc_c41e08b2",
    "fileName": "fattura-2026-000431.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 284419,
    "pages": 2,
    "classification": {
      "category": "fattura_fornitore",
      "confidence": 0.972,
      "model": "bp-doc-classifier-v4"
    },
    "extracted": {
      "numero_documento": "2026/000431",
      "data_documento": "2026-08-29",
      "fornitore": "Rossi Componenti S.p.A.",
      "partita_iva": "IT09876543210",
      "imponibile": 4520.5,
      "iva": 994.51,
      "totale": 5515.01,
      "valuta": "EUR"
    },
    "downloadUrl": "https://api.blupantheon.com/identity/v1/documents/bp_doc_c41e08b2/content",
    "downloadUrlExpiresAt": "2026-09-04T12:02:53.640Z",
    "sourceSessionId": null,
    "traceId": "trc_84cd21f0e6b74a19"
  }
}

URL di download temporanei

downloadUrl richiede comunque l'header Authorization con la chiave API e scade dopo un'ora. Scarica il file appena ricevuto l'evento, non conservare l'URL.

Ritentativi e backoff

Se l'endpoint non risponde 2xx entro 10 secondi, la consegna viene ripetuta.

TentativoRitardo dal fallimento precedenteTempo cumulato
1Immediato0
21 minuto1 minuto
35 minuti6 minuti
430 minuti36 minuti
52 ore2 ore e 36 minuti

Esaurito il quinto tentativo, la consegna passa in dead letter e viene emesso l'evento webhook.delivery.failed verso gli endpoint che lo hanno sottoscritto.

Codici che non vengono ritentati

Le risposte 410 Gone e 404 Not Found ripetute per oltre 24 ore causano la disattivazione automatica dell'endpoint. Riattivalo dalla Console dopo aver risolto.

Dead letter e replay

ConsoleConfigurazioneWebhookLog consegne

Le consegne in dead letter restano disponibili 30 giorni. Puoi:

  • filtrare per stato Fallita, tipo di evento o intervallo temporale;
  • ispezionare richiesta inviata, header, corpo e risposta ricevuta;
  • rilanciare una singola consegna con Riprova;
  • rilanciare in blocco tutte le consegne fallite di un intervallo con Riprova selezione.

Il replay genera una nuova consegna

Il replay usa un nuovo X-BP-Delivery ma mantiene lo stesso id dell'evento. Se la tua deduplica si basa solo su X-BP-Delivery, un replay verrà elaborato di nuovo. Per una deduplica logica usa anche il campo id.

Idempotenza in ricezione

Deduplica su due livelli
async function handleDelivery(deliveryId, payload) {
  // livello 1: la stessa consegna (ritentativo di rete)
  if (await deliveryStore.has(deliveryId)) {
    return { skipped: true, reason: 'delivery-duplicata' };
  }
  await deliveryStore.put(deliveryId, { ttlDays: 7 });

  // livello 2: lo stesso evento (replay manuale dalla Console)
  if (await eventStore.has(payload.id)) {
    return { skipped: true, reason: 'evento-gia-elaborato' };
  }
  await eventStore.put(payload.id, { ttlDays: 30 });

  await enqueueForRobot(payload);
  return { skipped: false };
}

Test in locale

Durante lo sviluppo l'endpoint gira sulla tua macchina. Esponilo con un tunnel.

# 1. installa e autentica (una volta sola)
brew install ngrok
ngrok config add-authtoken IL_TUO_TOKEN_NGROK

# 2. avvia il tuo receiver locale sulla porta 3000
node receiver.mjs

# 3. esponi la porta 3000 su HTTPS pubblico
ngrok http 3000

# 4. copia l URL mostrato, ad esempio:
#    https://a1b2-93-45-12-7.ngrok-free.app
#    e registralo in Console come:
#    https://a1b2-93-45-12-7.ngrok-free.app/webhooks/blupantheon

# 5. ispeziona le richieste ricevute
open http://127.0.0.1:4040

Mai tunnel in produzione

I tunnel di sviluppo cambiano URL a ogni riavvio e non offrono garanzie di disponibilità. In produzione usa un endpoint stabile con certificato TLS aziendale.

Ricevitore minimo di riferimento

receiver.mjs
import express from 'express';
import crypto from 'node:crypto';

const app = express();
const SECRET = process.env.BP_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;
const seenDeliveries = new Map();

app.post(
  '/webhooks/blupantheon',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.get('X-BP-Signature') ?? '';
    const deliveryId = req.get('X-BP-Delivery') ?? '';
    const rawBody = req.body.toString('utf8');

    const parts = Object.fromEntries(
      signature.split(',').map((part) => part.split('=')),
    );
    const timestamp = Number(parts.t);
    const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp);

    if (!Number.isFinite(timestamp) || age > TOLERANCE_SECONDS) {
      return res.status(400).send('timestamp fuori tolleranza');
    }

    const expected = crypto
      .createHmac('sha256', SECRET)
      .update(`${timestamp}.${rawBody}`, 'utf8')
      .digest('hex');

    const provided = Buffer.from(parts.v1 ?? '', 'utf8');
    const computed = Buffer.from(expected, 'utf8');

    if (
      provided.length !== computed.length ||
      !crypto.timingSafeEqual(provided, computed)
    ) {
      return res.status(401).send('firma non valida');
    }

    if (seenDeliveries.has(deliveryId)) {
      return res.status(202).send();
    }
    seenDeliveries.set(deliveryId, Date.now());

    const event = JSON.parse(rawBody);
    console.log(`Ricevuto ${event.event} (${event.id})`);
    queueMicrotask(() => processEvent(event));

    return res.status(202).send();
  },
);

function processEvent(event) {
  console.log('Elaborazione asincrona di', event.id);
}

app.listen(3000, () => console.log('Receiver in ascolto sulla porta 3000'));

Passi successivi

On this page