Riferimento API

Autenticazione, endpoint, payload dei webhook e verifica della firma per l'API di Salonnare.

Riferimento API

Non ancora disponibile

Questa funzionalità è ancora contrassegnata come Presto nell'app: al momento non puoi creare chiavi API o webhook. Se apri Impostazioni > API e Webhook, vedi solo un avviso che la funzionalità è in arrivo. Questa pagina descrive come funzionerà una volta disponibile.

Questa pagina è il riferimento tecnico per gli sviluppatori che costruiscono un'integrazione con l'API di Salonnare. Presuppone che tu sappia già cos'è una chiave API e un webhook, se cerchi la spiegazione in linguaggio semplice e le schermate per crearli, vedi API e Webhook.

Ogni affermazione qui sotto è presa direttamente dal codice sorgente del server Salonnare. Dove il codice e questa pagina dovessero mai non coincidere, fidati di uno sguardo aggiornato al codice: questo documento può diventare obsoleto, il codice è ciò che effettivamente viene eseguito.

Autenticazione

Invia la tua chiave API su ogni richiesta nell'header X-API-Key:

curl https://<your-salon>.salonnare.com/api/bookings \
  -H "X-API-Key: sk_live_a1b2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Formato della chiave

Una chiave si presenta così sk_live_<4 caratteri hex>_<48 caratteri hex>, per esempio:

sk_live_a1b2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Solo i primi 12 caratteri (sk_live_ più il segmento di 4 caratteri) vengono conservati o mostrati di nuovo dopo la creazione. I restanti 48 caratteri sono il vero segreto. Salonnare conserva un hash SHA-256 della chiave completa, mai la chiave stessa.

Ottenere una chiave

Le chiavi e i webhook sono gestiti da un amministratore del salone sotto Impostazioni -> API e Webhook nell'app. Quando crei una chiave scegli uno di tre livelli di permesso:

PermessoCosa permette oggi
read (predefinito)Solo GET/HEAD/OPTIONS. Qualsiasi altro metodo su qualsiasi endpoint restituisce 403.
writeIn pratica uguale a read: nessun endpoint accetta attualmente una richiesta di scrittura tramite una chiave API (vedi Endpoint di lettura sotto, tutte le sei route sono GET).
fullUguale a read/write oggi, per lo stesso motivo.

In altre parole: l'accesso in scrittura non esiste ancora, indipendentemente da quale livello di permesso ha una chiave. Non costruire un'integrazione che si aspetta di creare o modificare appuntamenti, clienti, trattamenti o collaboratori tramite l'API: ogni route che accetta una chiave API è di sola lettura, e una richiesta che tenta qualcosa diverso da GET su qualsiasi endpoint viene rifiutata prima di raggiungere la logica della route.

La chiave completa viene mostrata esattamente una volta, nella risposta della chiamata di creazione, nel momento in cui la crei. Conservala immediatamente (un gestore di segreti, non un messaggio di chat o un foglio di calcolo), Salonnare non può mostrartela di nuovo. Se la perdi, revocala e creane una nuova.

A quale salone parla una chiave

È la chiave stessa a determinare il salone (tenant), non il nome host che chiami. Puoi inviare richieste al sottodominio del tuo stesso salone (https://<your-salon>.salonnare.com/api/...), che è il modo consigliato e inequivocabile di farlo, ma il salone nella risposta è sempre quello a cui appartiene la chiave, indipendentemente da quale host Salonnare valido stai effettivamente chiamando.

Errori di autenticazione

SituazioneStatoCorpo
Nessun header X-API-Key e nessun token Authorization: Bearer401{"error": "Token ontbreekt.", "code": "unauthorized", "error_key": "server.errors.token_missing"}
Chiave sconosciuta, malformata o scaduta401{"error": "Invalid API key.", "code": "invalid_api_key"}
Il salone della chiave non è attivo (es. sospeso)403{"error": "Salon is not active.", "code": "tenant_inactive"}
Richiesta non GET/HEAD/OPTIONS con una chiave read403{"error": "This API key is read-only.", "code": "read_only_key"}

Una richiesta con una chiave API riceve messaggi di errore in inglese: i tre errori del livello delle chiavi nella tabella sopra sono sempre in inglese, e gli errori che passano per il meccanismo di traduzione (la maggior parte di quelli con un campo error_key) vengono restituiti in inglese per il traffico con chiave, qualunque Accept-Language tu invii. Una richiesta senza chiave (la prima riga) segue il suo header Accept-Language per le nove lingue supportate da Salonnare (nl, en, de, fr, es, it, pl, pt, tr) ed è in olandese negli altri casi. Alcuni messaggi su altri endpoint sono ancora stringhe fisse in olandese. Non fare affidamento su nessuno dei due comportamenti: basa la logica della tua integrazione sul codice di stato HTTP (e su code/error_key dove presenti), mai sul testo del messaggio. I corpi includono sempre error; diverse route aggiungono code, e alcune aggiungono anche error_key/error_params oltre a questo, analizza con tolleranza e non assumere un insieme fisso ed esatto di chiavi.

Endpoint di lettura

Una chiave API concede accesso in lettura a esattamente sei endpoint. Nessun'altra route accetta una chiave API, indipendentemente dal livello di permesso della chiave:

MetodoPercorsoRestituisce
GET/api/bookingsElenco degli appuntamenti
GET/api/bookings/:idUn appuntamento
GET/api/crm/clientsElenco dei clienti
GET/api/crm/clients/:idUn cliente
GET/api/servicesElenco dei trattamenti
GET/api/staffElenco dei collaboratori

A differenza del resto dell'app di amministrazione Salonnare, queste sei route non restituiscono la riga grezza del database a una chiave API. Ogni tipo di oggetto passa attraverso una funzione di formattazione fissa, toApiClient, toApiStaff, toApiBooking, toApiService, che emette solo un elenco di campi predefinito e versionato, in snake_case. Nulla fuori da quell'elenco viene mai incluso, indipendentemente da cosa altro contenga o acquisisca in futuro il record sottostante. Due delle quattro forme sono intenzionalmente identiche alla loro controparte webhook, campo per campo: toApiClient corrisponde al payload client.created/client.updated, e toApiBooking corrisponde al payload degli appuntamenti incluso il suo array services (entrambi usano la stessa lista consentita per le righe dei trattamenti). Se già analizzi i webhook di Salonnare, le risposte REST per clienti e appuntamenti non richiedono un parser separato.

Questo filtraggio si applica solo al traffico tramite chiave API. I collaboratori del salone collegati con il proprio account, chiamando esattamente gli stessi URL con un token Bearer invece di un header X-API-Key, ricevono ancora il record completo e non filtrato su cui si basa l'app stessa (che include campi interni che questi formattatori eliminano deliberatamente). La differenza è puramente il canale di accesso, non l'endpoint o i dati. Questo documento descrive solo la forma X-API-Key, quella che vedrà la tua integrazione.

GET /api/bookings

Parametri di query (tutti opzionali):

ParametroFormatoEffetto
from, toYYYY-MM-DDIntervallo di date semi-aperto sull'ora di inizio dell'appuntamento.
dateYYYY-MM-DDUn singolo giorno (stesso effetto di from=to=quel giorno).
sourcestringaFiltra su come è stato creato l'appuntamento (manual, online, google_reserve, ...).
location_idinteroFiltra su una singola sede.
client_idinteroFiltra sugli appuntamenti di un singolo cliente.
pageinteroAttiva la paginazione (vedi sotto). Senza di esso ottieni l'array completo.
limitinteroDimensione della pagina quando page è impostato. Predefinito 50, massimo 200.

Senza ?page=, la risposta è un array JSON semplice, limitato a 2000 righe, ordinato per impostazione predefinita dal più recente al più vecchio, tranne quando ordina dal più vecchio al più recente (cronologicamente) quando imposti sia from che to, oppure quando imposti date. Con ?page=N, la risposta è:

{
  "items": [ /* same objects as below */ ],
  "total": 134,
  "page": 1,
  "limit": 50,
  "pages": 3
}

Ogni elemento, in entrambe le modalità, è l'output di toApiBooking():

{
  "id": 4821,
  "client_id": 312,
  "staff_id": 7,
  "start_at": "2026-08-25T09:00:00.000Z",
  "end_at": "2026-08-25T09:45:00.000Z",
  "status": "scheduled",
  "notes": "",
  "services": [
    {
      "service_id": 41,
      "staff_id": 7,
      "duration_min": 45,
      "price": "45.00"
    }
  ]
}

Questo è l'insieme completo dei campi per questo endpoint, nessun nome del cliente, nessun dettaglio della cabina, nessun totale a livello di appuntamento. status è uno tra scheduled, provisional, completed, cancelled, no-show. Gli appuntamenti annullati e no-show sono inclusi nell'elenco, filtrali lato client su status se vuoi solo gli appuntamenti attivi. notes è sempre una stringa (stringa vuota, non null, quando non c'è nulla da mostrare).

services sono i trattamenti dell'appuntamento, nell'ordine in cui vengono eseguiti, ed è lo stesso array che inviano i webhook degli appuntamenti. Per riga: service_id (risolvi il nome con GET /api/services), staff_id (il collaboratore per quel trattamento specifico, che può differire dal staff_id dell'appuntamento), duration_min, e price, il prezzo come stringa decimale, fissato al momento in cui è stato creato l'appuntamento, così una modifica successiva del prezzo del trattamento non riscrive la storia. Un appuntamento ha sempre almeno una riga.

GET /api/bookings/:id

Nessun parametro di query. Restituisce un singolo oggetto nella stessa identica forma dell'elemento dell'elenco sopra:

{
  "id": 4821,
  "client_id": 312,
  "staff_id": 7,
  "start_at": "2026-08-25T09:00:00.000Z",
  "end_at": "2026-08-25T09:45:00.000Z",
  "status": "scheduled",
  "notes": "",
  "services": [
    {
      "service_id": 41,
      "staff_id": 7,
      "duration_min": 45,
      "price": "45.00"
    }
  ]
}

Qui non c'è nessun nome del cliente, nessuna cabina e nessuna informazione sul percorso (cure), la risposta tramite chiave API si ferma deliberatamente agli otto campi sopra, anche se la versione per utente connesso di questo endpoint restituisce un oggetto molto più ricco con dettagli della cabina per riga, collaboratori aggiuntivi per trattamento e il contesto del percorso dell'appuntamento. services contiene solo trattamenti effettivi: le righe di transizione che l'app usa per il tempo di posa (la colorazione, per esempio) vengono omesse, così l'array corrisponde esattamente al webhook degli appuntamenti. Aggiungere campi in seguito non è una modifica incompatibile, ma presumi che la risposta di oggi sia esattamente questa forma.

Un :id non numerico o non positivo restituisce 400 con {"error": "Invalid appointment ID.", "code": "bad_request", "error_key": "server.errors.invalid_booking_id_upper"}. Un :id valido ma inesistente restituisce 404 con {"error": "Appointment not found.", "code": "not_found", "error_key": "server.errors.booking_not_found"}.

GET /api/crm/clients

Parametri di query (tutti opzionali):

ParametroFormatoEffetto
searchstringaConfronta con nome, nome preferito, email e telefono.
label_idinteroFiltra sui clienti a cui è assegnata questa etichetta.
pageinteroAttiva la paginazione, stesso formato degli appuntamenti sopra.
limitinteroDimensione della pagina quando page è impostato. Predefinito 50, massimo 200.

Senza ?page=, la risposta è un array JSON semplice ordinato alfabeticamente (oppure per rilevanza di ricerca quando search è impostato). Ogni elemento è l'output di toApiClient():

{
  "id": 312,
  "name": "Anna Voorbeeld",
  "email": "anna@example.com",
  "phone": null,
  "mobile": "+31612345678",
  "first_name": "Anna",
  "last_name": "Voorbeeld",
  "preferred_name": null,
  "city": "Waalwijk",
  "country": "NL",
  "created_at": "2026-01-14T10:22:00.000Z"
}

Questo è l'insieme completo dei campi, nessun indirizzo, nessuna data di nascita, nessuna etichetta, nessun dato di fedeltà o no-show, e nessuno dei campi legati all'account/provider di pagamento (passwordHash, stripeCustomerId e simili) che in passato filtravano qui. search/label_id filtrano ancora quali clienti corrispondono; non aggiungono campi a ciò che viene restituito su ciascuno di essi.

GET /api/crm/clients/:id

Nessun parametro di query. Restituisce un singolo oggetto nella stessa identica forma dell'elemento dell'elenco sopra:

{
  "id": 312,
  "name": "Anna Voorbeeld",
  "email": "anna@example.com",
  "phone": null,
  "mobile": "+31612345678",
  "first_name": "Anna",
  "last_name": "Voorbeeld",
  "preferred_name": null,
  "city": "Waalwijk",
  "country": "NL",
  "created_at": "2026-01-14T10:22:00.000Z"
}

Le risposte dei moduli di anamnesi (intake_responses) non fanno mai parte di questa risposta per una chiave API, indipendentemente da chi è autorizzato a vederle nell'app, il campo semplicemente non è nell'elenco fisso sopra.

Un :id non numerico o zero/NaN restituisce 400 con {"error": "Invalid client ID.", "code": "bad_request", "error_key": "server.errors.invalid_client_id"}. Un :id valido ma inesistente restituisce 404 con {"error": "Client not found.", "code": "not_found", "error_key": "server.errors.not_found", "error_params": {"entity": "Client"}}.

GET /api/services

Parametri di query:

ParametroFormatoEffetto
bookable1 o trueSolo trattamenti attivi (non archiviati). Senza di esso, vengono inclusi anche i trattamenti archiviati.

La risposta è sempre un array JSON semplice (nessun supporto per la paginazione su questo endpoint). Ogni elemento è l'output di toApiService():

[
  {
    "id": 41,
    "name": "Knipbeurt",
    "duration_min": 45,
    "price": "45.00",
    "category_id": 3,
    "active": true
  }
]

Questo è l'insieme completo dei campi, nessuna descrizione, nessun codice articolo, nessuna aliquota IVA, nessuna restrizione di pianificazione, e nessun nome/colore della categoria (solo il category_id grezzo, se ti serve il dettaglio della categoria, recuperalo separatamente o mantieni una tua mappatura).

GET /api/staff

Nessun parametro di query. La risposta è sempre un array JSON semplice (nessun supporto per la paginazione). Ogni elemento è l'output di toApiStaff():

[
  {
    "id": 7,
    "name": "Chris Medewerker",
    "email": "chris@example.com",
    "role": "staff"
  }
]

Questo è l'insieme completo dei campi, nessun iban/bic, nessuno stato di Stripe Connect, nessuno stato di blocco/tentativo di accesso, nessun flag di permesso delle pagine. Se la tua integrazione ha bisogno dei dettagli di pagamento, non è qualcosa che una chiave API può leggere; deve provenire direttamente dal salone.

Non c'è deliberatamente nessun campo active: l'endpoint restituisce sempre solo i collaboratori attivi del salone, quindi il campo non potrebbe mai essere altro che true. Non aspettare un active: false per considerare un collaboratore uscito: un collaboratore che se ne va semplicemente smette di comparire in questo elenco, quindi considera "assente dalla risposta" come il segnale.

Limite di frequenza

Ogni richiesta /api/*, incluse le richieste tramite chiave API, è limitata a 200 richieste al minuto per combinazione di salone e indirizzo IP. Nessuno dei sei endpoint di lettura ha un limitatore aggiuntivo e più stretto oltre a questo.

Quando lo superi, ottieni:

  • Stato 429
  • Corpo: {"error": "Too many requests, please try again later", "code": "rate_limited", "error_key": "server.errors.rate_limited_comma"}
  • Header su ogni risposta (non solo quando sei limitato): X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (timestamp Unix, secondi)
  • Sul 429 stesso, un header aggiuntivo Retry-After con il numero di secondi da attendere prima di riprovare

Rispetta Retry-After invece di effettuare polling a intervalli fissi, riflette il tempo di reset effettivo della finestra che hai raggiunto.

Webhook

Invece del polling, puoi registrare un URL e far inviare da Salonnare gli eventi non appena accadono. I webhook si configurano nell'app sotto Impostazioni -> API e Webhook -> Webhook, lo stesso posto dove si trovano le chiavi API.

Busta

Ogni consegna è un POST con questo corpo JSON:

{
  "event": "booking.created",
  "data": { /* event-specific payload, see below */ },
  "timestamp": "2026-08-25T09:00:03.512Z"
}

Insieme al corpo, ogni consegna porta due header: X-Webhook-Signature (vedi Verifica delle firme) e X-Webhook-Event (lo stesso valore di event nel corpo).

Eventi

Puoi sottoscrivere qualsiasi combinazione di sei eventi. Leggi la colonna "Si attiva quando" alla lettera: ognuno di questi eventi si attiva solo da un'azione di un collaboratore del salone nell'app, e ciascuno ha esattamente un punto di attivazione nel server:

EventoSi attiva quando
booking.createdUn collaboratore crea un appuntamento nell'app (POST /api/bookings)
booking.updatedUn collaboratore modifica un appuntamento nell'app (PATCH/PUT /api/bookings/:id), incluso un cambio di stato a completato o no-show, poiché l'app lo fa tramite la stessa chiamata
booking.cancelledUn collaboratore annulla un appuntamento nell'app (DELETE /api/bookings/:id)
payment.receivedUn collaboratore completa un pagamento alla cassa (POST /api/pos/orders)
client.createdUn collaboratore crea un cliente nell'app (POST /api/crm/clients)
client.updatedUn collaboratore modifica un cliente nell'app (PATCH/PUT /api/crm/clients/:id)

Cosa non attiva un evento

Questa è la parte che spesso spiazza le integrazioni, quindi viene esplicitata invece che lasciata implicita. Gli appuntamenti, i clienti e i pagamenti che nascono in qualsiasi altro modo non producono nessun webhook. Concretamente, non viene inviato nulla quando:

  • un cliente prenota online tramite la pagina di prenotazione del salone, oppure annulla o conferma un appuntamento lì stesso;
  • un appuntamento arriva tramite Google Reserve;
  • una voce in lista d'attesa viene convertita in un appuntamento;
  • una serie ricorrente genera i propri appuntamenti;
  • un percorso (cure) viene creato, o una sua seduta viene annullata;
  • un appuntamento provvisorio viene annullato automaticamente perché la scadenza dell'acconto è passata, oppure promosso a confermato perché l'acconto è stato pagato;
  • un appuntamento viene segnato come completato come effetto collaterale di un pagamento alla cassa;
  • un cliente si crea da solo sulla pagina di prenotazione, si registra tramite Google/Microsoft/Apple/Facebook, oppure viene creato da un'importazione;
  • un record cliente cambia per qualsiasi motivo diverso dalla schermata di modifica cliente, il ricalcolo del livello fedeltà, il credito, le risposte del modulo di anamnesi, un'email rimbalzata, oppure viene anonimizzato da una richiesta di cancellazione.

La conseguenza pratica: non puoi usare i webhook come un flusso completo delle modifiche. Per un salone che accetta prenotazioni online, la maggior parte dei nuovi appuntamenti non raggiungerà mai il tuo endpoint. Riconcilia periodicamente con GET /api/bookings usando una finestra from/to e considera i webhook come un suggerimento a bassa latenza sopra a quello, non come la fonte di verità.

Tutti i sei payload passano attraverso una lista fissa di campi consentiti sul server, solo i campi elencati sotto vengono inviati, in snake_case, indipendentemente da cosa altro contenga il record sottostante. Questo è un contratto deliberato: i payload dei webhook non acquisiranno silenziosamente nuovi campi dal database.

booking.created / booking.updated / booking.cancelled:

{
  "event": "booking.created",
  "data": {
    "id": 4821,
    "client_id": 312,
    "staff_id": 7,
    "start_at": "2026-08-25T09:00:00.000Z",
    "end_at": "2026-08-25T09:45:00.000Z",
    "status": "scheduled",
    "notes": "",
    "services": [
      {
        "service_id": 41,
        "staff_id": 7,
        "duration_min": 45,
        "price": "45.00"
      }
    ]
  },
  "timestamp": "2026-08-25T09:00:03.512Z"
}

Tutti e tre gli eventi degli appuntamenti inviano esattamente questa forma, costruita dall'appuntamento memorizzato: stessi campi, stessi tipi, start_at/end_at sempre in UTC. Due dettagli da sapere:

  • status è lo stato al momento in cui viene inviato l'evento. Un booking.created per un appuntamento che richiede un acconto riporta quindi provisional, non scheduled, e un booking.cancelled riporta cancelled invece dello stato che aveva l'appuntamento prima.
  • Questo è lo stesso insieme di campi di GET /api/bookings/:id, array services incluso, quindi un solo parser copre entrambi i canali.

payment.received:

{
  "event": "payment.received",
  "data": {
    "order_id": 9931,
    "order_number": "2026-000482",
    "total": 45,
    "payment_method": "card",
    "client_id": 312
  },
  "timestamp": "2026-08-25T09:12:47.001Z"
}

client.created / client.updated:

{
  "event": "client.created",
  "data": {
    "id": 312,
    "name": "Anna Voorbeeld",
    "email": "anna@example.com",
    "phone": null,
    "mobile": "+31612345678",
    "first_name": "Anna",
    "last_name": "Voorbeeld",
    "preferred_name": null,
    "city": "Waalwijk",
    "country": "NL",
    "created_at": "2026-01-14T10:22:00.000Z"
  },
  "timestamp": "2026-08-25T09:00:03.512Z"
}

Nota che questo è esattamente lo stesso insieme di campi della risposta GET /api/crm/clients sopra, toWebhookClient() e toApiClient() condividono la stessa lista consentita, quindi un cliente appare identico sia che tu lo riceva tramite webhook sia che tu lo recuperi tramite l'API.

Quando un proprietario di salone invia una consegna di test manuale dalla schermata Webhook, riceverai una busta con "event": "test" e un piccolo payload segnaposto. Non è uno dei sei eventi sottoscrivibili, fai in modo che il tuo gestore ignori (senza generare errore) i nomi di evento che non riconosce.

Verifica delle firme

Ogni consegna viene firmata con HMAC-SHA256 sul corpo grezzo della richiesta, usando il segreto del webhook. Come una chiave API, questo segreto viene cifrato a riposo e viene mostrato per intero solo una volta: nella risposta della chiamata di creazione, e di nuovo se il salone lo rigenera tramite POST /api/developer/webhooks/:id/rotate-secret. GET/PUT su un webhook non includono mai il segreto, solo un booleano hasSecret, quindi se il salone lo ha perso, loro (o tu, per loro conto) devono rigenerarlo e aggiornare qualunque cosa verifichi la firma sul tuo lato. Il digest hex viene inviato nell'header X-Webhook-Signature.

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

rawBody deve essere esattamente i byte che Salonnare ha inviato sulla rete, non JSON.stringify(JSON.parse(rawBody)), e non un oggetto che hai ri-serializzato da qualche parte. Ri-serializzare cambia l'ordine delle chiavi e gli spazi bianchi, il che cambia i byte, il che cambia la firma. Questo è l'errore di integrazione più comune in assoluto e produce una firma che non corrisponde mai, senza un messaggio di errore chiaro che indichi la causa. Leggi il corpo come una stringa/buffer grezzo prima che il tuo framework lo analizzi come JSON, e calcola l'hash su quello.

crypto.timingSafeEqual genera un errore (invece di restituire false) quando i due buffer differiscono in lunghezza di byte, quindi un header X-Webhook-Signature mancante o malformato farà andare in crash il verify() sopra invece di far semplicemente fallire il controllo. Proteggiti da questo: verifica che l'header sia presente e sia una stringa hex di 64 caratteri prima di chiamare timingSafeEqual, oppure racchiudi la chiamata in un try/catch.

Comportamento di consegna

  • Un solo tentativo, nessun nuovo tentativo. Se il tuo endpoint è offline o genera errori, Salonnare non accoda né riprova quella consegna. Progetta la tua integrazione per tollerare un evento occasionalmente mancato (per esempio, riconciliando periodicamente con GET /api/bookings usando una finestra from/to) invece di assumere che ogni evento arrivi.
  • Timeout di 10 secondi. Se il tuo endpoint non risponde entro 10 secondi, la consegna viene considerata fallita.
  • I reindirizzamenti non vengono seguiti. Una risposta 3xx conta come consegna fallita, esattamente come una 4xx/5xx. Rispondi 2xx dall'esatto URL che hai registrato.
  • Disattivazione automatica dopo 10 fallimenti consecutivi. Una volta che un webhook è fallito 10 volte di fila (timeout, risposte non 2xx, e URL bloccati/non validi contano tutti), Salonnare lo disattiva automaticamente. Il proprietario del salone deve riattivarlo dalla schermata Webhook; riattivarlo azzera il contatore dei fallimenti.
  • I log di consegna sono disponibili per il salone, non per il tuo endpoint. Un amministratore del salone può controllare le ultime consegne (codice di stato, corpo della risposta, durata) per un webhook tramite GET /api/developer/webhooks/:id/logs nell'app, questa chiamata richiede un accesso da amministratore (non una chiave API), quindi serve al salone per fare il debug della propria integrazione, non è qualcosa che chiama il tuo servizio di ricezione.

Requisiti dell'URL

L'URL del webhook che registri deve essere raggiungibile pubblicamente su internet. Salonnare lo valida sia quando lo salvi sia di nuovo, in modo aggiornato, appena prima di ogni singola consegna (per proteggersi da un dominio che risolve a qualcosa di sicuro oggi e a qualcosa di interno domani). Un URL viene rifiutato, al momento del salvataggio con un 400, oppure al momento della consegna come tentativo fallito, quando è o risolve a:

  • Qualsiasi cosa diversa da http:/https: (in produzione, solo https:)
  • localhost, 127.0.0.1, 0.0.0.0, ::1, :: o qualsiasi equivalente IPv6 tra parentesi
  • Un nome host interno (server, db, mariadb, mysql, redis, docs, worker)
  • Un indirizzo IP privato/loopback/link-local letterale (intervalli RFC 1918, 169.254.0.0/16, 127.0.0.0/8, IPv6 ULA fc00::/7, IPv6 link-local fe80::/10)
  • Un indirizzo IPv6 in una delle notazioni di transizione che possono nascondere un indirizzo IPv4 nei suoi bit inferiori: IPv4-mapped (::ffff:0:0/96), IPv4-compatible (::/96), NAT64 (64:ff9b::/96) e 6to4 (2002::/16). Questi sono bloccati sul prefisso, quindi ogni variante dell'indirizzo incorporato viene rifiutata, [::ffff:127.0.0.1], [::127.0.0.1] e [::7f00:1] allo stesso modo. Il normale IPv6 global-unicast non è interessato
  • Un nome host pubblico il cui record DNS risolve a uno qualsiasi dei casi sopra, controllato al momento della consegna, non solo una volta alla registrazione, specificamente in modo che un dominio che punti in seguito a un indirizzo interno venga comunque bloccato

Punta l'URL verso un dominio che controlli fin dall'inizio; non registrare un segnaposto aspettandoti di ripuntare in seguito il DNS verso un indirizzo interno, e non fare affidamento solo sul controllo al momento del salvataggio, la consegna rivalida ogni volta.

Articoli correlati