Blu Pantheon
مرجع API

Riferimento webhook

Schema degli eventi, catalogo completo, payload di esempio, firma HMAC, ritentativi e coda dei messaggi non recapitati.

I webhook notificano il tuo sistema in tempo reale quando accade qualcosa su Blu Pantheon, evitando il polling degli endpoint di lettura. Ogni notifica è una richiesta POST con body JSON verso l'URL che hai configurato, firmata con HMAC-SHA256.

Per la configurazione degli endpoint di ricezione vedi Webhook; per gli scenari di automazione vedi Automazioni.

Envelope dell'evento

Tutti gli eventi condividono la stessa struttura. I dati specifici stanno in data, i metadati fuori.

Envelope
{
  "id": "evt_5a9c2f18",
  "type": "session.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:12:44.118Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {},
  "previousAttributes": null,
  "traceId": "01J8ZQ3K5V6X7Y8Z9A0B1C2D3E"
}

Campi dell'envelope

ParametroTipoObblig.DefaultDescrizione
idstringSì—Identificativo univoco dell’evento. Usalo come chiave di deduplica: lo stesso id può arrivare più volte.
typestringSì—Tipo di evento nel formato risorsa.azione, ad esempio session.created.
apiVersionstringSì—Versione dello schema del payload, congelata al momento della creazione della sottoscrizione.
createdAtstringSì—Istante di generazione dell’evento in formato ISO 8601 UTC. Non coincide con l’istante di consegna.
tenantIdstringSì—Tenant a cui appartiene l’evento. Verificalo se ricevi eventi di più tenant sullo stesso endpoint.
livemodebooleanSì—False per gli eventi generati in sandbox, true in produzione.
dataobjectSì—Rappresentazione della risorsa al momento dell’evento. La struttura dipende dal tipo.
previousAttributesobject | nullNo—Valori precedenti dei soli campi modificati. Valorizzato negli eventi di tipo updated.
traceIdstringSì—Identificativo di correlazione della richiesta che ha generato l’evento.

Header della richiesta

Header inviati con ogni notifica

ParametroTipoObblig.DefaultDescrizione
X-BP-SignaturestringSì—Firma HMAC-SHA256 nel formato t=<unix>,v1=<hex>. Verificala prima di elaborare il body.
X-BP-EventstringSì—Tipo di evento, identico al campo type del body. Utile per instradare senza deserializzare.
X-BP-DeliverystringSì—Identificativo del singolo tentativo di consegna. Cambia a ogni ritentativo, a differenza di id.
X-BP-TimestampnumberSì—Timestamp Unix in secondi del tentativo di consegna. Rifiuta scarti superiori a 300 secondi.
X-BP-AttemptnumberSì—Numero progressivo del tentativo, da 1 a 8.
User-AgentstringSì—Sempre BluPantheon-Webhooks/1.0.
Content-TypestringSì—Sempre application/json; charset=utf-8.
Esempio di richiesta in ingresso
POST /webhooks/blupantheon HTTP/1.1
Host: integrazioni.acme.it
Content-Type: application/json; charset=utf-8
User-Agent: BluPantheon-Webhooks/1.0
X-BP-Event: session.created
X-BP-Delivery: dlv_3f7a1c60
X-BP-Timestamp: 1788515564
X-BP-Attempt: 1
X-BP-Signature: t=1788515564,v1=9c4f2a...

Versionamento dei payload

Ogni sottoscrizione è ancorata a una versione dello schema, indicata in apiVersion. La versione viene fissata quando crei la sottoscrizione e non cambia automaticamente.

ComportamentoEffetto sulla versione
Aggiunta di un campo in dataNessuno, viene distribuito su tutte le versioni
Aggiunta di un nuovo tipo di eventoNessuno, va sottoscritto esplicitamente
Rinomina o rimozione di un campoNuova versione dello schema
Cambio di tipo di un campoNuova versione dello schema

Ignora i campi sconosciuti

Il tuo consumatore deve tollerare campi aggiuntivi non previsti: possono comparire in qualsiasi momento senza cambio di versione. Non usare deserializzatori configurati per fallire su proprietà sconosciute.

L'aggiornamento a una nuova versione si effettua dalla Console.

ConsoleConfigurazioneWebhookEndpointVersione schema

Catalogo degli eventi

EventoDominioAttivazione
session.createdSessioniUna nuova conversazione viene avviata su qualunque canale
session.message.createdSessioniViene aggiunto un messaggio alla conversazione
session.closedSessioniLa conversazione viene chiusa manualmente o per inattività
session.abandonedSessioniL'utente interrompe la conversazione senza chiuderla
session.ratedSessioniIl cliente lascia una valutazione al termine della conversazione
session.taggedSessioniViene applicata o rimossa un'etichetta
handover.requestedHandoverViene richiesto il passaggio a un operatore umano
handover.assignedHandoverUn operatore prende in carico la conversazione
handover.completedHandoverL'operatore restituisce la conversazione o la chiude
handover.expiredHandoverNessun operatore ha preso in carico entro il tempo massimo
client.createdClientiViene creata una nuova anagrafica cliente
client.updatedClientiUn campo dell'anagrafica viene modificato
client.mergedClientiDue anagrafiche duplicate vengono unificate
client.deletedClientiL'anagrafica viene eliminata definitivamente
booking.createdPrenotazioniViene creata una prenotazione in stato pending
booking.confirmedPrenotazioniLa prenotazione viene confermata
booking.rescheduledPrenotazioniData o ora della prenotazione vengono modificate
booking.cancelledPrenotazioniLa prenotazione viene annullata
booking.reminderPrenotazioniScatta il promemoria configurato prima dell'appuntamento
document.uploadedDocumentiUn documento viene caricato in una knowledge base
document.indexedDocumentiL'indicizzazione è completata e il documento è utilizzabile
document.failedDocumentiL'elaborazione del documento non è riuscita
document.deletedDocumentiUn documento viene rimosso dalla knowledge base
invoice.issuedFatturazioneViene emessa una nuova fattura
invoice.paidFatturazioneIl pagamento della fattura viene registrato
invoice.payment_failedFatturazioneIl tentativo di addebito non è andato a buon fine
subscription.updatedFatturazioneCambia piano, ciclo o stato dell'abbonamento
usage.threshold_reachedFatturazioneIl consumo supera una soglia configurata del piano
user.invitedIdentitàUn utente viene invitato nel tenant
user.activatedIdentitàL'utente completa l'attivazione dell'account
user.deactivatedIdentitàL'utente viene disattivato
api_key.revokedSistemaUna chiave API viene revocata
project.publishedSistemaUn progetto viene pubblicato o ripubblicato

Payload di esempio

session.created

Quando scatta: quando una nuova conversazione viene avviata su qualunque canale

{
  "id": "evt_5a9c2f18",
  "type": "session.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:12:44.118Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "sess_9f2c8d41",
    "projectId": "bp_proj_7f3a9c21",
    "clientId": "cli_5e1a7b30",
    "channel": "web",
    "status": "open",
    "language": "it",
    "metadata": {
      "sourceUrl": "https://www.acme.it/supporto",
      "userAgent": "Mozilla/5.0",
      "utmCampaign": "settembre-2026"
    },
    "startedAt": "2026-09-04T09:12:44.006Z"
  },
  "previousAttributes": null,
  "traceId": "01J8ZQ3K5V6X7Y8Z9A0B1C2D3E"
}
session.message.created

Quando scatta: quando viene aggiunto un messaggio alla conversazione

{
  "id": "evt_7b1e4c93",
  "type": "session.message.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:12:46.980Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "msg_4f9a2d18",
    "sessionId": "sess_9f2c8d41",
    "projectId": "bp_proj_7f3a9c21",
    "role": "assistant",
    "content": "Certamente. Mi conferma la ragione sociale e la partita IVA?",
    "citations": [
      { "documentId": "doc_2e8b4c07", "title": "Listino 2026", "score": 0.87 }
    ],
    "tokensUsed": 184,
    "createdAt": "2026-09-04T09:12:46.914Z"
  },
  "previousAttributes": null,
  "traceId": "01J8ZQ3K5V6X7Y8Z9A0B1C2D3F"
}
session.closed

Quando scatta: quando la conversazione viene chiusa manualmente o per inattività

{
  "id": "evt_2d8f0a71",
  "type": "session.closed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:02:11.640Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "sess_9f2c8d41",
    "projectId": "bp_proj_7f3a9c21",
    "clientId": "cli_5e1a7b30",
    "status": "closed",
    "reason": "resolved",
    "messageCount": 14,
    "metrics": {
      "durationSeconds": 1698,
      "averageResponseMs": 1240,
      "tokensUsed": 8412
    },
    "closedAt": "2026-09-04T10:02:11.556Z"
  },
  "previousAttributes": { "status": "open" },
  "traceId": "01J8ZQ4M7X2P9Q1R3S5T7U9V1W"
}
handover.requested

Quando scatta: quando viene richiesto il passaggio a un operatore umano

{
  "id": "evt_9c3a7e02",
  "type": "handover.requested",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:44:18.302Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "hnd_7a3c5e01",
    "sessionId": "sess_9f2c8d41",
    "projectId": "bp_proj_7f3a9c21",
    "clientId": "cli_5e1a7b30",
    "queue": "vendite",
    "priority": "high",
    "reason": "Richiesta di sconto fuori listino",
    "status": "pending",
    "position": 2,
    "context": { "dealSize": "50 licenze" },
    "requestedAt": "2026-09-04T09:44:18.220Z"
  },
  "previousAttributes": null,
  "traceId": "01J8ZQ5N8Y3R0S2T4U6V8W0X2Y"
}
handover.assigned

Quando scatta: quando un operatore prende in carico la conversazione

{
  "id": "evt_1e6b9d40",
  "type": "handover.assigned",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T09:46:03.117Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "hnd_7a3c5e01",
    "sessionId": "sess_9f2c8d41",
    "queue": "vendite",
    "status": "assigned",
    "operator": {
      "id": "usr_4a7b1e90",
      "email": "mario.rossi@acme.it",
      "displayName": "Mario Rossi"
    },
    "waitSeconds": 105,
    "assignedAt": "2026-09-04T09:46:03.041Z"
  },
  "previousAttributes": { "status": "pending", "position": 2 },
  "traceId": "01J8ZQ6P9Z4S1T3U5V7W9X1Y3Z"
}
client.created

Quando scatta: quando viene creata una nuova anagrafica cliente

{
  "id": "evt_6f2c1a85",
  "type": "client.created",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:32:15.560Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "cli_5e1a7b30",
    "email": "mario.rossi@acme.it",
    "firstName": "Mario",
    "lastName": "Rossi",
    "phoneNumber": "+390212345678",
    "company": "ACME S.p.A.",
    "externalId": "CRM-88213",
    "source": "web",
    "tags": ["lead-inbound"],
    "consent": { "marketing": false, "profiling": false },
    "createdAt": "2026-09-04T10:32:15.482Z"
  },
  "previousAttributes": null,
  "traceId": "01J8ZQ7Q0A5T2U4V6W8X0Y2Z4A"
}
client.updated

Quando scatta: quando un campo dell'anagrafica cliente viene modificato

{
  "id": "evt_8a4d3b70",
  "type": "client.updated",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T11:11:02.712Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "cli_5e1a7b30",
    "email": "mario.rossi@acme.it",
    "phoneNumber": "+390687654321",
    "tags": ["cliente-storico", "rinnovo-2027"],
    "updatedAt": "2026-09-04T11:11:02.640Z"
  },
  "previousAttributes": {
    "phoneNumber": "+390212345678",
    "tags": ["cliente-storico"]
  },
  "traceId": "01J8ZQ8R1B6U3V5W7X9Y1Z3A5B"
}
booking.confirmed

Quando scatta: quando una prenotazione passa in stato confermato

{
  "id": "evt_3c9e5f21",
  "type": "booking.confirmed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:35:02.844Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "bkg_2a6f9c34",
    "serviceId": "svc_1d9a4f70",
    "serviceName": "Consulenza commerciale",
    "resourceId": "res_5c2e8a13",
    "clientId": "cli_5e1a7b30",
    "sessionId": "sess_9f2c8d41",
    "status": "confirmed",
    "start": "2026-09-08T07:00:00Z",
    "end": "2026-09-08T07:30:00Z",
    "timezone": "Europe/Rome",
    "confirmationCode": "ACME-4821",
    "confirmedAt": "2026-09-04T10:35:02.771Z"
  },
  "previousAttributes": { "status": "pending" },
  "traceId": "01J8ZQ9S2C7V4W6X8Y0Z2A4B6C"
}
booking.cancelled

Quando scatta: quando una prenotazione viene annullata e lo slot torna disponibile

{
  "id": "evt_4d0f6a32",
  "type": "booking.cancelled",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-05T08:12:40.096Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "bkg_2a6f9c34",
    "serviceId": "svc_1d9a4f70",
    "clientId": "cli_5e1a7b30",
    "status": "cancelled",
    "reason": "client_request",
    "note": "Il cliente ha chiesto di rimandare.",
    "cancelledBy": "usr_4a7b1e90",
    "start": "2026-09-08T07:00:00Z",
    "cancelledAt": "2026-09-05T08:12:40.019Z"
  },
  "previousAttributes": { "status": "confirmed" },
  "traceId": "01J8ZQAT3D8W5X7Y9Z1A3B5C7D"
}
document.indexed

Quando scatta: quando l'indicizzazione di un documento è completata

{
  "id": "evt_5e1a7b43",
  "type": "document.indexed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:34:58.201Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "doc_2e8b4c07",
    "dataStoreId": "ds_3c8f1a60",
    "title": "Listino 2026",
    "fileName": "listino-2026.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 1843205,
    "status": "ready",
    "pageCount": 24,
    "chunkCount": 187,
    "language": "it",
    "processingSeconds": 162,
    "indexedAt": "2026-09-04T10:34:58.140Z"
  },
  "previousAttributes": { "status": "processing" },
  "traceId": "01J8ZQBU4E9X6Y8Z0A2B4C6D8E"
}
document.failed

Quando scatta: quando l'elaborazione di un documento non riesce

{
  "id": "evt_6f2b8c54",
  "type": "document.failed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T10:36:12.508Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "doc_9a1d3e78",
    "dataStoreId": "ds_3c8f1a60",
    "fileName": "catalogo-scansionato.pdf",
    "status": "failed",
    "error": {
      "code": "UNREADABLE_CONTENT",
      "message": "Il PDF non contiene testo estraibile. Attiva il riconoscimento ottico dei caratteri."
    },
    "failedAt": "2026-09-04T10:36:12.441Z"
  },
  "previousAttributes": { "status": "processing" },
  "traceId": "01J8ZQCV5F0Y7Z9A1B3C5D7E9F"
}
invoice.paid

Quando scatta: quando il pagamento di una fattura viene registrato

{
  "id": "evt_7a3c9d65",
  "type": "invoice.paid",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-01T06:04:33.201Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "id": "inv_0f3d8b52",
    "number": "2026/000418",
    "status": "paid",
    "subscriptionId": "sub_4b7e1a09",
    "total": { "amount": 60878, "currency": "EUR" },
    "taxAmount": { "amount": 10978, "currency": "EUR" },
    "paymentMethod": { "type": "sepa_debit", "last4": "4821" },
    "periodStart": "2026-09-01T00:00:00Z",
    "periodEnd": "2026-10-01T00:00:00Z",
    "pdfUrl": "https://files.blupantheon.com/invoices/inv_0f3d8b52.pdf?sig=...",
    "paidAt": "2026-09-01T06:04:33.117Z"
  },
  "previousAttributes": { "status": "open" },
  "traceId": "01J8ZQDW6G1Z8A0B2C4D6E8F0G"
}
usage.threshold_reached

Quando scatta: quando il consumo supera una soglia configurata del piano

{
  "id": "evt_8b4d0e76",
  "type": "usage.threshold_reached",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-04T13:02:00.114Z",
  "tenantId": "tnt_2b8e4f11",
  "livemode": true,
  "data": {
    "metric": "sessions",
    "threshold": 0.8,
    "used": 16041,
    "included": 20000,
    "unit": "sessione",
    "periodStart": "2026-09-01T00:00:00Z",
    "periodEnd": "2026-10-01T00:00:00Z",
    "plan": "business",
    "overagePolicy": "billed"
  },
  "previousAttributes": null,
  "traceId": "01J8ZQEX7H2A9B1C3D5E7F9G1H"
}

Firma e verifica

L'header X-BP-Signature ha due componenti separate da virgola:

Formato
X-BP-Signature: t=1788515564,v1=9c4f2a1e8b7d0345f6a9c2e1b8d7043a5f6c9e2b1d8a7043f5c6e9b2d1a8074f
ComponenteSignificato
tTimestamp Unix in secondi del tentativo di consegna
v1HMAC-SHA256 esadecimale minuscolo, versione 1 dello schema di firma

La stringa firmata è la concatenazione del timestamp, un punto e il corpo grezzo della richiesta:

Stringa firmata
${timestamp}.${rawBody}

Usa il body grezzo

La verifica deve avvenire sui byte esatti ricevuti, prima di qualsiasi parsing JSON. Se il framework deserializza automaticamente il body e tu lo riserializzi per calcolare l'HMAC, la firma non corrisponderà mai: l'ordine delle chiavi e la spaziatura cambiano.

Passaggi di verifica:

Estrai le componenti

Separa t e v1 dall'header. Se una delle due manca, rifiuta la richiesta con 400.

Verifica la finestra temporale

Rifiuta le richieste con t che differisce di oltre 300 secondi dall'ora corrente: è la difesa contro gli attacchi di replay.

Calcola e confronta

Calcola HMAC-SHA256(segreto, "${t}.${rawBody}") e confrontalo con v1 usando un confronto a tempo costante. Non usare l'operatore di uguaglianza tra stringhe.

Rispondi rapidamente

Restituisci 2xx entro 5 secondi e processa l'evento in modo asincrono. Un'elaborazione lenta viene interpretata come fallimento e genera ritentativi.

L'implementazione completa nei vari linguaggi, con la rotazione dei segreti, è documentata in Firma HMAC.

Ritentativi

Una consegna è considerata riuscita solo con status 2xx. Qualsiasi altro status, timeout o errore di connessione avvia la sequenza di ritentativi con backoff esponenziale.

TentativoAttesa dal precedenteTempo trascorso
1Immediato0
230 secondi30 secondi
32 minuticirca 2 minuti
410 minuticirca 12 minuti
51 oracirca 1 ora
66 orecirca 7 ore
724 orecirca 31 ore
848 orecirca 79 ore
Risposta del tuo endpointComportamento
2xxConsegna completata
3xxFallimento: i redirect non vengono seguiti
410 GoneConsegna abbandonata immediatamente, nessun ritentativo
4xx (altri)Ritentativo secondo la tabella
5xxRitentativo secondo la tabella
Timeout oltre 5 secondiRitentativo secondo la tabella

Consegna almeno una volta

Lo stesso evento può essere consegnato più volte, anche se il tuo endpoint ha risposto correttamente (ad esempio se la risposta è andata persa). Il tuo consumatore deve essere idempotente: usa il campo id dell'envelope come chiave di deduplica e ignora gli eventi già elaborati. L'header X-BP-Delivery cambia a ogni tentativo e non va usato per la deduplica.

Coda dei messaggi non recapitati

Dopo l'ottavo tentativo fallito l'evento viene spostato nella coda dei messaggi non recapitati e vi resta per 30 giorni.

ConsoleConfigurazioneWebhookConsegne non riuscite

Comportamento della sospensione automatica:

  • Se un endpoint fallisce tutte le consegne per 24 ore consecutive viene sospeso automaticamente.
  • Alla sospensione vengono notificati via email gli amministratori del tenant.
  • Gli eventi generati durante la sospensione vengono comunque accodati per 30 giorni.
  • La riattivazione è manuale e avviene dalla Console dopo aver risolto il problema.

Replay

Puoi rinviare eventi già consegnati o falliti, dalla Console o via API. È l'operazione da usare dopo un fermo del tuo sistema o dopo la correzione di un bug nel consumatore.

POST/console/v1/webhooks/{endpointId}/replayBearer JWT

Rinvia gli eventi selezionati all'endpoint indicato. Accetta Idempotency-Key.

Richiesta
{
  "eventIds": ["evt_5a9c2f18", "evt_7b1e4c93"],
  "from": "2026-09-04T00:00:00Z",
  "to": "2026-09-04T23:59:59Z",
  "eventTypes": ["session.created", "session.closed"],
  "onlyFailed": true
}
202 Accepted
{
  "replayId": "rpl_1b8c4e07",
  "endpointId": "whe_6d2f9a31",
  "eventCount": 128,
  "status": "queued",
  "createdAt": "2026-09-05T09:00:12.447Z"
}

Vincoli del replay:

  • Massimo 1.000 eventi per singola operazione.
  • Finestra massima di 30 giorni.
  • Gli eventi rinviati mantengono id e createdAt originali, ma ricevono un nuovo X-BP-Delivery.
  • Il campo X-BP-Attempt riparte da 1.

Verifica l'idempotenza prima di un replay massivo

Un replay di centinaia di eventi su un consumatore non idempotente produce duplicati in cascata nel tuo sistema. Prima di lanciarlo, verifica su un singolo evento che l'elaborazione ripetuta non generi effetti collaterali.

Collegamenti

في هذه الصفحة