Comportamento della consegna

Il payload standard, il limite di 5 secondi, il motivo per cui non sono previsti nuovi tentativi e il significato di ogni stato di consegna.

La consegna dei webhook è volutamente semplice, e questa semplicità ha conseguenze per il tuo ricevitore: un solo tentativo HTTP, cinque secondi, nessun nuovo tentativo. Leggi questa pagina prima di decidere quante operazioni eseguire in modo sincrono nel tuo gestore.

Il payload standard

Ogni evento tranne listing.submission arriva con la stessa struttura al livello principale, con i campi specifici dell'evento annidati in data.

JSON
{  "event": "connection.location_connected",  "timestamp": "2026-08-25T14:32:10Z",  "account_id": 11073,  "location_id": "279381",  "data": {    "platform": "google",    "connected_account_id": "ba03bc4b-9f8c-4d3b-8e93-9628034c63cc"  }}
CampoTipoNote
eventstringIl nome dell'evento con le parti separate da punti. Usa questo valore per scegliere il ramo di esecuzione.
timestampstringData e ora in formato ISO-8601 UTC, assegnate quando è stata preparata la consegna, non quando è avvenuta la modifica sottostante.
account_idintegerL'account a cui appartiene l'evento.
location_idstringUna stringa numerica: "279381", non 279381. La conversione non viene eseguita per interaction.*; vedi l'eccezione qui sotto.
dataobjectCampi specifici dell'evento. Documentati per ciascun evento nella documentazione di riferimento degli eventi.
agency_account_idintegerPresente solo quando l'account è gestito da un account agenzia superiore.

I campi facoltativi all'interno di data vengono omessi quando sono vuoti, anziché essere inviati come null. Scrivi il codice in modo da gestire i casi imprevisti: tratta allo stesso modo una chiave assente e un valore null esplicito, e non dare mai per scontato che una chiave documentata come facoltativa sia presente.

Due eccezioni

Il limite di 5 secondi

Ogni consegna consiste in un singolo POST con un timeout di connessione di 5 secondi e un timeout di lettura di 5 secondi. Se il tuo endpoint non risponde in tempo, il tentativo viene abbandonato e registrato come timeout.

Questo limite di tempo si applica all'intera esecuzione del tuo gestore. L'unica soluzione sicura è:

  1. Verifica la firma.
  2. Salva il payload in un sistema di archiviazione persistente: una coda, una tabella, un registro.
  3. Restituisci 200.

Tutte le altre operazioni (arricchimento dei dati, distribuzione a più destinatari, chiamate alla REST API, qualsiasi operazione che coinvolga terze parti) devono essere eseguite dopo il prelievo dalla coda.

Non vengono effettuati nuovi tentativi

Ogni consegna viene tentata esattamente una volta. Non sono previsti intervalli di attesa progressivi, code per nuovi tentativi o code per le consegne non riuscite. Se il tuo endpoint non è disponibile per due minuti, gli eventi generati in quei due minuti vanno persi. Non arriveranno in seguito.

Due conseguenze da chiarire esplicitamente:

  • I reindirizzamenti non vengono seguiti. I reindirizzamenti sono completamente disabilitati, quindi una risposta 301 o 302 del tuo endpoint viene registrata come consegna non riuscita, senza seguire il reindirizzamento a un secondo URL. Imposta come URL dei webhook l'URL finale. Fai attenzione ai reindirizzamenti dal dominio senza prefisso a www e a quelli che aggiungono una barra finale, che molti server web applicano per impostazione predefinita.
  • Solo 2xx viene considerato una consegna riuscita. 3xx, 4xx e 5xx vengono tutti registrati come errori di consegna.

Duplicati e ordine

  • Puoi ricevere lo stesso evento due volte e lo stesso id per due modifiche diverse. Gli identificatori all'interno di data (un id di interazione, un id di elemento di un post, un id di account collegato) identificano la risorsa, non la modifica. interaction.review viene emesso di nuovo con lo stesso id di interazione ogni volta che la recensione cambia e nessun payload contiene una versione o un campo con la data dell'ultimo aggiornamento, quindi un gestore che scarta definitivamente gli eventi in base a id + event elimina modifiche effettive senza segnalarlo. Rileva invece i duplicati esatti: calcola un hash dei byte del corpo originale e ignora le ripetizioni dello stesso hash entro una finestra temporale breve e limitata (minuti, non per sempre). Un corpo diverso per lo stesso id è una nuova modifica, non un duplicato. Poi rileggi la risorsa tramite l'API REST e considera quella lettura come fonte autorevole; usa l'id della risorsa come chiave per la scrittura, invece di saltarla.
  • L'ordine non è garantito e timestamp non risolve il problema. Gli eventi provengono da diversi flussi indipendenti e vengono consegnati man mano che arrivano, quindi un local_post.published può arrivare prima del local_post.created che ti aspettavi per primo. Non ordinare due modifiche alla stessa risorsa in base a timestamp: viene assegnato quando viene preparata la consegna, non quando è avvenuta la modifica, e il payload non contiene alcuna versione o numero di sequenza. Quando l'ordine è rilevante, rileggi la risorsa e usa ciò che restituisce l'API.
  • A profile.updated viene applicato un debounce che limita le consegne a una per sede ogni 60 secondi, sul fronte iniziale. Si tratta di una soppressione effettiva, non di un ritardo: la prima modifica apre la finestra e viene consegnata, ogni modifica all'interno della finestra viene scartata e non arriva mai, e changed_fields descrive solo la modifica che ha aperto la finestra. L'evento ti arriva quindi all'inizio della finestra, perciò una rilettura attivata dal suo arrivo può vedere soltanto quella prima modifica. Pianifica una seconda rilettura dopo la chiusura della finestra, almeno 60 secondi dopo l'evento, ed esegui anche una riconciliazione periodica delle sedi, perché la notifica stessa può andare persa: un solo tentativo, nessun nuovo tentativo. Il debounce non viene applicato a nient'altro.

Recupero dopo un evento non ricevuto

Non sono previsti nuovi tentativi né la riproduzione degli eventi, quindi per ogni famiglia serve una lettura autorevole a cui poter ricorrere. Esegui la riconciliazione a intervalli programmati, non solo quando qualcosa sembra non funzionare.

FamigliaRileggi conIntervallo consigliatoCosa non può recuperare una lettura
listing.submissionGET /locations/{locationId}/listings/premium, una volta con listingType=premium e una volta con additional: i valori di syncStatus e listingUrl per ciascun sito segnalati dall'evento.Ogni giorno per ciascuna sede e dopo una modifica collettiva delle sedi.Il payload legacy non contiene né timestamp né account_id, quindi un evento non ricevuto non può essere collocato nel tempo né attribuito a un account. error_message relativo a un invio incomplete non è esposto da alcuna lettura.
profile.*GET /locations, GET /locations/search o GET /locations-by-ids. Il valore di updatedAt su ciascuna sede indica quali sedi sono cambiate.Ogni ora, oltre alla rilettura dopo la chiusura della finestra descritta sopra.source (user / api / bulk_edit) e changed_fields: una lettura mostra lo stato attuale, mai chi ha modificato cosa. Le modifiche soppresse dal debounce di 60 secondi non lasciano alcuna traccia e lo stato precedente all'eliminazione di una sede non è più disponibile.
connection.*GET /connected-accounts e GET /connected-accounts/{connectedAccountId}/details per connessione, disconnessione e stato dell'autenticazione. Per la coppia di eventi di verifica Google, leggi googleVerificationStatus { status, message } da qualsiasi lettura di una sede.Ogni ora; ogni pochi minuti mentre è in corso una verifica.failure_type (rejected rispetto a transient) e la stringa reason. Le interruzioni temporanee non sono visibili: una connessione interrotta e poi ripristinata risulta connessa alla lettura.
interaction.*GET /locations/{locationId}/reviews per singola sede, GET /rollup_interactions su più sedi, GET /reviewDetails per id.Ogni 15 minuti per le nuove recensioni, ogni giorno per un controllo più ampio.Il fatto che qualcosa sia cambiato. I campi documentati delle interazioni non contengono una data dell'ultimo aggiornamento, quindi una recensione modificata si presenta esattamente come una che hai già memorizzato: confronta i campi che conservi.
local_post.*GET /locations/{locationId}/posts, GET /locations/{locationId}/bulk-posts, GET /posts/{postId} per lo stato attuale di pubblicazione su ciascun sito.Ogni ora mentre la pubblicazione dei post è in corso.rejection_reason così come è stato consegnato e la sequenza di stati attraversata da un elemento di un post. Le letture restituiscono lo stato attuale, non le transizioni.
review_analytics.*GET /locations/{locationId}/review-analytics-overview con startDate e endDate del periodo.Ogni giorno dopo la finestra di aggregazione, ogni settimana per la famiglia settimanale.I valori numerici al momento dell'istantanea. Il riepilogo viene ricalcolato al momento della lettura, quindi una recensione arrivata in ritardo o eliminata lo fa differire dai dati contenuti nell'istantanea.

Esiti della consegna

Ogni tentativo, anche quando non viene inviata alcuna richiesta, viene registrato con uno stato. Queste sono le stringhe esatte.

I quattro esiti e le stringhe di stato che li registrano. «Bloccato» e «Limite di frequenza superato» indicano che non è stata effettuata alcuna richiesta HTTP, perciò è utile distinguerli da un errore effettivo.
StatoEsitoSignificatoCosa fare
successConsegnatoIl tuo endpoint ha restituito un codice 2xx.Nulla.
un codice HTTP come stringa, e.g. "404", "500"Non riuscitoIl tuo endpoint ha restituito quel codice non appartenente alla classe 2xx. Anche 3xx rientra in questo caso, perché i reindirizzamenti non vengono seguiti.Controlla il percorso, la catena di reindirizzamenti e il tasso di errore del tuo handler.
999Non riuscitoÈ stata ricevuta una risposta senza un codice di stato utilizzabile.Di solito la causa è un proxy o un bilanciatore di carico posto davanti al tuo endpoint.
timeoutNon riuscitoNessuna risposta entro il limite di 5 secondi.Sposta l'elaborazione al di fuori della gestione della richiesta e conferma prima la ricezione.
network_errorNon riuscitoNon è stato possibile risolvere il nome dell'host o raggiungerlo.Controlla il DNS e verifica che il nome dell'host sia risolvibile pubblicamente.
errorNon riuscitoIl tentativo ha generato un errore diverso da quelli elencati sopra, ad esempio un errore di handshake TLS.Controlla la catena dei certificati e i log del server.
blocked_urlBloccatoL'URL non ha superato il controllo di sicurezza al momento della connessione: non usa HTTPS, non contiene un host oppure è stato risolto in un indirizzo privato, di loopback, link-local o di metadati.Imposta l'URL in modo che punti a un host HTTPS pubblico. Tieni presente che questo controllo viene ripetuto a ogni consegna, quindi una modifica al DNS può causare il blocco di un URL che prima funzionava.
endpoint_unverifiedBloccatoL'account ha un segreto di firma, ma l'endpoint non ha superato la verifica oppure la verifica è stata reimpostata.Esegui di nuovo Verifica endpoint dalla dashboard.
rate_limitedLimite di frequenza superatoÈ stato superato il limite al minuto o al giorno. L'evento è stato scartato.Riduci il volume o passa a un piano con un limite più alto. Consulta piani e limiti.

Una lista di controllo per il ricevitore

  • Verifica la firma di ogni richiesta; se non corrisponde, restituisci 401 e non elaborare nulla.
  • Calcola l'hash dei byte del corpo originale, mai di un oggetto serializzato di nuovo.
  • Restituisci 2xx in un tempo ben inferiore a 5 secondi; metti l'elaborazione in coda.
  • Usa gli ID contenuti in data come chiave per ogni scrittura dell'handler, in modo che una ripetizione produca lo stesso risultato, ma non considerare mai un ID ripetuto come un'indicazione che non ci sia nulla da fare.
  • Non dedurre l'ordine degli eventi dall'ordine di arrivo e non ordinarli in base a timestamp.
  • Normalizza location_id come stringa alla ricezione.
  • Ignora i valori di event che non riconosci e restituisci comunque 200.
  • Rendi disponibile l'URL dei webhook come URL finale, senza reindirizzamenti.
  • Riconcilia periodicamente i dati con quelli dell'API REST; non sono previsti nuovi tentativi su cui fare affidamento.