Perilaku pengiriman

Payload standar, batas waktu 5 detik, alasan tidak ada percobaan ulang, dan arti setiap status pengiriman.

Mekanisme pengiriman webhook sengaja dibuat sederhana, dan kesederhanaan ini memiliki konsekuensi bagi penerima Anda: satu percobaan HTTP, lima detik, tanpa percobaan ulang. Baca halaman ini sebelum menentukan seberapa banyak pekerjaan yang dijalankan langsung oleh handler Anda.

Payload standar

Setiap peristiwa kecuali listing.submission diterima dengan struktur tingkat teratas yang sama, dengan field khusus peristiwa tersebut di dalam 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"  }}
FieldTipeCatatan
eventstringNama peristiwa yang dipisahkan dengan titik. Gunakan nilai ini untuk menentukan percabangan.
timestampstringISO-8601 UTC, dicatat saat pengiriman disiapkan, bukan saat perubahan yang mendasarinya terjadi.
account_idintegerAkun yang terkait dengan peristiwa tersebut.
location_idstringString numerik: "279381", bukan 279381. Tidak dikonversi secara paksa pada interaction.*; lihat pengecualian di bawah.
dataobjectField khusus peristiwa. Didokumentasikan untuk setiap peristiwa dalam referensi peristiwa.
agency_account_idintegerHanya disertakan jika akun dikelola oleh akun agensi induk.

Field opsional di dalam data tidak disertakan jika kosong, alih-alih dikirim sebagai null. Tulis kode yang mengantisipasi kondisi ini: perlakukan key yang tidak ada dan nilai null yang diberikan secara eksplisit dengan cara yang sama, dan jangan pernah menganggap key yang didokumentasikan sebagai opsional akan selalu ada.

Dua pengecualian

Batas waktu 5 detik

Setiap pengiriman berupa satu POST dengan batas waktu koneksi 5 detik dan batas waktu pembacaan 5 detik. Jika endpoint Anda belum merespons dalam waktu yang ditentukan, percobaan dihentikan dan dicatat sebagai timeout.

Batas waktu tersebut mencakup seluruh proses handler Anda. Satu-satunya rancangan yang aman adalah:

  1. Verifikasi tanda tangan.
  2. Simpan payload di penyimpanan persisten: antrean, tabel, atau log.
  3. Kembalikan 200.

Semua pekerjaan lainnya (pengayaan data, pendistribusian ke beberapa tujuan, pemanggilan kembali REST API, atau apa pun yang melibatkan pihak ketiga) harus dilakukan setelah data diambil dari antrean tersebut.

Tidak ada percobaan ulang

Pengiriman dicoba tepat satu kali. Tidak ada backoff, antrean percobaan ulang, ataupun dead-letter queue. Jika endpoint Anda tidak tersedia selama dua menit, peristiwa yang dipicu dalam dua menit tersebut akan hilang. Peristiwa tersebut tidak akan diterima kemudian.

Dua konsekuensi yang perlu dijelaskan secara tegas:

  • Pengalihan tidak diikuti. Pengalihan dinonaktifkan sepenuhnya, sehingga 301 atau 302 dari endpoint Anda dicatat sebagai pengiriman yang gagal, tanpa mengikuti pengalihan ke URL kedua. Konfigurasikan URL webhook Anda sebagai URL akhir. Perhatikan pengalihan dari domain tanpa subdomain ke www dan pengalihan yang menambahkan garis miring di akhir URL, yang ditambahkan secara default oleh banyak server web.
  • Hanya 2xx yang dianggap berhasil dikirim. 3xx, 4xx, dan 5xx semuanya dicatat sebagai kegagalan.

Duplikasi dan urutan

  • Anda mungkin menerima event yang sama dua kali, serta id yang sama untuk dua perubahan berbeda. Pengidentifikasi di dalam data (id interaksi, id item postingan, id akun terhubung) mengidentifikasi sumber daya, bukan perubahannya. interaction.review dipicu kembali dengan id interaksi yang sama setiap kali ulasan berubah, dan tidak ada payload yang memuat versi atau field waktu pembaruan, sehingga handler yang secara permanen mengabaikan event berdasarkan id + event akan membuang perubahan yang sebenarnya tanpa diketahui. Sebagai gantinya, deteksi duplikat yang persis sama: hitung hash dari byte mentah body dan abaikan pengulangan hash yang sama dalam rentang waktu singkat yang dibatasi (hitungan menit, bukan selamanya). Body yang berbeda untuk id yang sama merupakan perubahan baru, bukan duplikat. Kemudian baca kembali sumber daya dari REST API dan gunakan hasil pembacaan tersebut sebagai acuan utama; gunakan id sumber daya sebagai kunci saat menulis, alih-alih melewatkan penulisan.
  • Urutan tidak dijamin, dan timestamp tidak akan mengatasinya. Event berasal dari beberapa jalur independen dan dikirim sesuai waktu kedatangannya, sehingga local_post.published dapat tiba sebelum local_post.created yang Anda harapkan tiba lebih dahulu. Jangan mengurutkan dua perubahan pada sumber daya yang sama berdasarkan timestamp: waktu tersebut dicatat saat pengiriman disiapkan, bukan saat perubahan terjadi, dan tidak ada versi atau nomor urut di bagian mana pun dalam payload. Jika urutan penting, baca kembali sumber daya dan gunakan hasil yang dikembalikan API.
  • profile.updated dibatasi dengan debounce menjadi satu pengiriman per lokasi per 60 detik, pada awal rentang waktu. Mekanisme ini benar-benar menggabungkan beberapa perubahan menjadi satu pengiriman, bukan menundanya: perubahan pertama membuka rentang waktu dan dikirim, setiap perubahan dalam rentang tersebut dibuang dan tidak pernah tiba, dan changed_fields hanya menjelaskan perubahan yang membuka rentang tersebut. Karena itu, event sampai kepada Anda pada awal rentang waktu, sehingga pembacaan ulang yang dipicu oleh kedatangannya hanya dapat melihat perubahan pertama tersebut. Jadwalkan pembacaan ulang kedua setelah rentang waktu berakhir, setidaknya 60 detik setelah event, dan lakukan juga rekonsiliasi lokasi secara berkala, karena notifikasi itu sendiri dapat hilang: hanya satu upaya, tanpa percobaan ulang. Debounce tidak diterapkan pada hal lainnya.

Pemulihan dari event yang terlewat

Tidak ada percobaan ulang maupun pemutaran ulang, sehingga setiap kelompok event memerlukan pembacaan dari sumber acuan utama yang dapat Anda gunakan sebagai cadangan. Lakukan rekonsiliasi secara terjadwal, bukan hanya saat ada sesuatu yang tampak salah.

KelompokBaca kembali denganInterval yang disarankanHal yang tidak dapat dipulihkan melalui pembacaan
listing.submissionGET /locations/{locationId}/listings/premium, sekali dengan listingType=premium dan sekali dengan additional: syncStatus dan listingUrl per situs yang diberitahukan oleh event.Setiap hari untuk setiap lokasi, dan setelah pengeditan lokasi secara massal.Payload lama tidak memiliki timestamp maupun account_id, sehingga waktu terjadinya event yang terlewat tidak dapat ditentukan dan event tersebut tidak dapat dikaitkan dengan suatu akun. error_message pada pengajuan incomplete tidak tersedia melalui pembacaan apa pun.
profile.*GET /locations, GET /locations/search, atau GET /locations-by-ids. updatedAt pada setiap lokasi menunjukkan lokasi mana yang berubah.Setiap jam, ditambah pembacaan ulang setelah rentang waktu berakhir seperti yang dijelaskan di atas.source (user / api / bulk_edit) dan changed_fields: pembacaan menunjukkan keadaan saat ini, tetapi tidak pernah menunjukkan siapa yang mengubah apa. Perubahan yang dibuang oleh debounce 60 detik tidak meninggalkan jejak sama sekali, dan keadaan lokasi sebelum dihapus sudah tidak tersedia.
connection.*GET /connected-accounts dan GET /connected-accounts/{connectedAccountId}/details untuk penyambungan, pemutusan koneksi, dan kondisi autentikasi. Untuk pasangan event verifikasi Google, baca googleVerificationStatus { status, message } dari hasil pembacaan lokasi mana pun.Setiap jam; setiap beberapa menit saat verifikasi sedang berlangsung.failure_type (rejected dibandingkan dengan transient) dan string reason. Koneksi yang terputus lalu tersambung kembali tidak dapat diketahui: koneksi tersebut terbaca sebagai terhubung.
interaction.*GET /locations/{locationId}/reviews per lokasi, GET /rollup_interactions di seluruh lokasi, GET /reviewDetails berdasarkan id.Setiap 15 menit untuk ulasan baru, setiap hari untuk pemeriksaan yang lebih luas.Fakta bahwa ada sesuatu yang berubah. Field interaksi yang didokumentasikan tidak memuat waktu pembaruan, sehingga ulasan yang diedit tampak persis seperti ulasan yang sudah Anda simpan: bandingkan field yang Anda simpan.
local_post.*GET /locations/{locationId}/posts, GET /locations/{locationId}/bulk-posts, GET /posts/{postId} untuk status publikasi saat ini per situs.Setiap jam selama postingan sedang diproses.rejection_reason sebagaimana dikirimkan, dan urutan status yang dilalui suatu item postingan. Pembacaan memberikan status saat ini, bukan transisi antarstatus.
review_analytics.*GET /locations/{locationId}/review-analytics-overview dengan startDate dan endDate untuk periode tersebut.Setiap hari setelah rentang waktu agregasi berakhir, setiap minggu untuk kelompok event mingguan.Angka pada saat snapshot dibuat. Ringkasan dihitung ulang saat dibaca, sehingga ulasan yang terlambat masuk atau dihapus membuatnya berbeda dari isi snapshot.

Hasil pengiriman

Setiap upaya pengiriman, termasuk yang tidak pernah mengirimkan permintaan keluar dari sistem, dicatat dengan status. Berikut adalah string persisnya.

Empat hasil dan string status yang mencatatnya. 'Diblokir' dan 'dibatasi laju' berarti tidak ada permintaan HTTP yang dibuat sama sekali, sehingga keduanya perlu dibedakan dari kegagalan yang sebenarnya.
StatusHasilArtinyaTindakan yang perlu dilakukan
successTerkirimEndpoint Anda mengembalikan 2xx.Tidak ada.
kode HTTP sebagai string, e.g. "404", "500"GagalEndpoint Anda mengembalikan kode non-2xx tersebut. 3xx juga termasuk di sini karena pengalihan tidak diikuti.Periksa jalur, rangkaian pengalihan, dan tingkat kesalahan handler Anda.
999GagalRespons diterima tanpa kode status yang dapat digunakan.Biasanya disebabkan oleh proxy atau load balancer di depan endpoint Anda.
timeoutGagalTidak ada respons dalam batas waktu 5 detik.Pindahkan pekerjaan ke luar alur penanganan permintaan dan kirim konfirmasi penerimaan lebih cepat.
network_errorGagalNama host tidak dapat diresolusi atau host tidak dapat dijangkau.Periksa DNS dan pastikan nama host dapat diresolusi secara publik.
errorGagalUpaya tersebut menghasilkan kesalahan yang tidak termasuk dalam kategori di atas, seperti kegagalan handshake TLS.Periksa rantai sertifikat dan log server Anda.
blocked_urlDiblokirURL tidak lolos pemeriksaan keamanan saat koneksi akan dibuat: tidak menggunakan HTTPS, tidak memiliki host, atau diresolusi ke alamat privat, loopback, link-local, atau metadata.Arahkan URL ke host HTTPS publik. Perhatikan bahwa pemeriksaan ini diulang pada setiap pengiriman, sehingga perubahan DNS dapat menyebabkan URL yang sebelumnya berfungsi mulai diblokir.
endpoint_unverifiedDiblokirAkun memiliki kunci rahasia penandatanganan, tetapi endpoint belum lolos verifikasi, atau verifikasinya telah direset.Jalankan kembali Verifikasi endpoint dari dasbor.
rate_limitedDibatasi lajuBatas per menit atau per hari telah terlampaui. Peristiwa tersebut dibuang.Kurangi volume atau beralih ke paket dengan batas yang lebih tinggi. Lihat paket dan batas.

Daftar periksa penerima

  • Verifikasi tanda tangan pada setiap permintaan; kembalikan 401 dan jangan proses apa pun jika tanda tangan tidak cocok.
  • Hitung hash dari byte mentah isi permintaan, jangan pernah dari objek yang diserialisasi ulang.
  • Kembalikan 2xx dalam waktu jauh di bawah 5 detik; masukkan pekerjaan ke antrean.
  • Gunakan ID di dalam data sebagai kunci untuk setiap operasi tulis handler agar pengulangan menghasilkan hasil yang sama, tetapi jangan pernah menganggap ID yang berulang berarti tidak ada yang perlu dikerjakan.
  • Jangan simpulkan urutan dari urutan kedatangan, dan jangan urutkan berdasarkan timestamp.
  • Normalisasikan location_id menjadi string saat diterima.
  • Abaikan nilai event yang tidak Anda kenali, dan tetap kembalikan 200.
  • Layani URL webhook sebagai URL akhir, tanpa pengalihan.
  • Lakukan rekonsiliasi secara berkala dengan REST API; tidak ada percobaan ulang yang dapat memulihkan pengiriman yang gagal.