Skip to main content
Un webhook è una richiesta POST che dentalspace manda a un tuo indirizzo HTTPS quando succede qualcosa nella clinica: un appuntamento prenotato, una fattura pagata, un messaggio WhatsApp ricevuto. Così il tuo programma non deve chiedere ogni minuto se è cambiato qualcosa.

Destinazioni

Una destinazione è un indirizzo più l’elenco degli eventi che vuole ricevere. Si crea con l’API:
La risposta contiene secret, il segreto di firma (whsec_…). Si vede solo lì: conservalo subito. Per creare o modificare una destinazione servono webhooks:write e lo scope di ogni evento scelto: senza, 403 insufficient_scope con gli scope mancanti in details.requiredScopes. Se poi la chiave viene revocata o scade, la destinazione smette di ricevere.

Richiesta

La consegna riesce se rispondi 2xx entro 10 secondi. Un redirect (3xx) è un fallimento: non viene seguito. Del tuo indirizzo si registra solo lo status HTTP, mai il corpo della risposta.

Busta

data non contiene mai dati sanitari, e le risorse collegate sono solo id. Per i dettagli rileggi la risorsa con la tua chiave.

Catalogo degli eventi

Per ricevere qualunque evento la chiave deve avere anche webhooks:read. Due tipi non si sottoscrivono:
  • webhook.test: arriva solo quando lo chiedi con POST /webhook-endpoints/{id}/test, con data vuoto;
  • events.skipped: vedi sotto.

Eventi non nati

Se un evento non può nascere, al suo posto ne nasce uno events.skipped, così sai che ti manca qualcosa invece di non accorgertene. data.reason dice perché: data.type è il tipo dell’evento mancato e data.scope lo scope che serve per vederlo. Una destinazione riceve events.skipped se è iscritta a data.type. Quando lo ricevi, risincronizza con updatedSince sugli elenchi. Vedi Paginazione.

Firma

La firma è un HMAC-SHA256 calcolato con il segreto su questa stringa, quattro righe separate da \n:
Il risultato, in esadecimale minuscolo, viaggia in X-DentalSpace-Signature con il prefisso v1=. Il timestamp è dentro la firma: una richiesta ripetuta con un timestamp diverso non verifica.

Verifica

  1. Leggi il corpo come byte grezzi, prima di qualsiasi parsing JSON.
  2. Rifiuta la richiesta se X-DentalSpace-Timestamp dista più di 5 minuti dall’ora attuale.
  3. Calcola la firma attesa sulla stringa v1\n<timestamp>\n<idempotency-key>\n<corpo>.
  4. Confrontala con ciascuna firma dell’header, a tempo costante. Ne basta una valida.
  5. Se X-DentalSpace-Idempotency-Key l’hai già elaborata, rispondi 200 senza rifare il lavoro.

Rotazione del segreto

POST /webhook-endpoints/{id}/rotate-secret genera un segreto nuovo e lo mostra una volta sola. Il vecchio resta valido per overlapMinutes minuti, da 0 a 10080 (una settimana), predefinito 1440 (un giorno). In quel periodo ogni webhook porta due firme: v1=<nuova>,v1=<vecchia>. Aggiorna il segreto nel tuo programma prima di previousSecretExpiresAt. Con overlapMinutes: 0 il vecchio smette subito.

Ritentativi

Una consegna fallita (rete, timeout, risposta diversa da 2xx) viene ritentata. Corpo e X-DentalSpace-Idempotency-Key restano uguali; cambiano timestamp e firma. Le attese variano del 10% in più o in meno, perché i ritentativi di molte consegne non arrivino tutti insieme. Dopo il quinto fallimento la consegna diventa discarded. Dopo 50 consegne scartate di fila la destinazione si spegne da sola: enabled: false, disabledReason: "too_many_failures". Una consegna riuscita azzera il conteggio. Per riaccenderla: PATCH /webhook-endpoints/{id} con enabled: true. Ogni clinica ha al massimo 5 consegne in corso alla volta, e le cliniche sono servite a turno: un tuo indirizzo lento rallenta solo le tue consegne.

Storico e consegne

Con una chiave di prova (dsk_test_) il test non spedisce niente e risponde skipped: "test_mode". Cancellata una destinazione, le sue consegne restano leggibili per 30 giorni.