Tanggapi peristiwa dengan webhook
Atur URL webhook, selesaikan uji verifikasi, periksa tiap tanda tangan, konfirmasi penerimaan dalam 5 detik, dan pulihkan peristiwa terlewat lewat polling.
Webhook mengirimkan HTTP POST ke server Anda saat ada perubahan pada akun Anda: ulasan diterima, profil bisnis ditayangkan, postingan dipublikasikan. Ada satu URL per akun dan tidak ada langganan per peristiwa, sehingga satu penerima menerima semua 18 peristiwa yang didokumentasikan dan menentukan cabang pemrosesan berdasarkan field event.
1. Atur URL dan rahasia di dasbor
Bangun penerima pada langkah 2 dan 3 terlebih dahulu. Pembuatan rahasia mewajibkan penandatanganan dan menjeda pengiriman hingga endpoint Anda lulus verifikasi, sehingga endpoint yang belum siap menghambat setiap peristiwa.
- Deploy endpoint HTTPS. Alamat privat ditolak dan pengalihan tidak diikuti, jadi simpan URL akhir tanpa pengalihan
wwwatau pengalihan garis miring penutup sebelum URL tersebut. 2. Simpan URL di halaman Webhook pada dasbor. 3. Buat rahasia penandatanganan. Rahasia ini ditampilkan satu kali. Simpan sebagaiLISTINGSAPI_WEBHOOK_SECRETdi server Anda. 4. Klik Verifikasi endpoint. Platform mengirimkan tantangan dari langkah 2 dan menunggu hingga 10 detik.
Mengubah URL atau membuat ulang rahasia akan mereset verifikasi, jadi deploy rahasia baru terlebih dahulu, lalu verifikasi kembali. Detailnya tersedia di Ikhtisar webhook.
2. Jawab tantangan verifikasi
Tantangan tersebut berupa POST bertanda tangan dengan nama peristiwa yang dicadangkan dan sebuah nonce. Balas dengan 200 yang berisi HMAC-SHA256 dari nonce saja, dalam format heksadesimal dengan huruf kecil.
{ "event": "endpoint.verification", "nonce": "9f2c1e642b1a4b0e9f1a2c7d5e8a1b3c", "timestamp": "2026-08-25T14:32:10Z"}3. Verifikasi tanda tangan pada setiap pengiriman
Setiap permintaan bertanda tangan memuat X-ListingsAPI-Signature: sha256=<base64 HMAC-SHA256 of the raw body>. Hitung digest yang sama dari byte persis seperti yang Anda terima, bandingkan dalam waktu konstan, dan kembalikan 401 jika tidak cocok. Jangan parse lalu serialisasi ulang JSON terlebih dahulu; tindakan tersebut mengubah byte. Kedua penerima di bawah ini menyimpan body mentah, melakukan verifikasi, menjawab tantangan, dan menyimpan peristiwa secara persisten sebelum membalas.
import base64import hashlibimport hmacimport os from flask import Flask, abort, request app = Flask(__name__)SECRET = os.environ["LISTINGSAPI_WEBHOOK_SECRET"].encode() def signature_is_valid(raw_body: bytes, header: str | None) -> bool: digest = hmac.new(SECRET, raw_body, hashlib.sha256).digest() expected = "sha256=" + base64.b64encode(digest).decode() return hmac.compare_digest(header or "", expected) @app.post("/webhooks/listingsapi")def webhooks(): raw = request.get_data() # raw bytes, exactly as received if not signature_is_valid(raw, request.headers.get("X-ListingsAPI-Signature")): abort(401) body = request.get_json(silent=True) or {} # One-time handshake: hex digest of the nonce, not the body. if body.get("event") == "endpoint.verification": return hmac.new(SECRET, body["nonce"].encode(), hashlib.sha256).hexdigest(), 200 # Real event: durable write first, then acknowledge. Swap for your queue. inbox.put(raw) # must be committed before the 200 goes out return {"received": True}, 200import crypto from 'crypto';import express from 'express'; const app = express();const SECRET = process.env.LISTINGSAPI_WEBHOOK_SECRET as string; // express.raw keeps req.body as the untouched Buffer the HMAC covers.app.post('/webhooks/listingsapi', express.raw({ type: '*/*' }), async (req, res) => {const raw: Buffer = req.body;const digest = crypto.createHmac('sha256', SECRET).update(raw).digest('base64');const expected = Buffer.from('sha256=' + digest);const given = Buffer.from(req.get('X-ListingsAPI-Signature') ?? '');if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) { return res.status(401).send('bad signature');} const body = JSON.parse(raw.toString('utf8')); // One-time handshake: hex digest of the nonce, not the body.if (body.event === 'endpoint.verification') { const answer = crypto.createHmac('sha256', SECRET).update(body.nonce).digest('hex'); return res.status(200).send(answer);} // Real event: durable write first, then acknowledge. Swap for your queue.await inbox.put(raw); // must be committed before the 200 goes outreturn res.status(200).json({ received: true });});Jika suatu akun belum pernah membuat rahasia, pengiriman akan diterima tanpa tanda tangan. Jangan membangun integrasi dengan mengandalkan mode tersebut; buat rahasia setelah penerima di atas aktif.
4. Konfirmasikan penerimaan dengan cepat dan proses secara asinkron
Satu pengiriman berupa satu POST dengan batas waktu koneksi 5 detik dan batas waktu baca 5 detik, yang dicoba tepat satu kali. Tidak ada percobaan ulang, backoff, atau pemutaran ulang. Jika handler Anda membutuhkan waktu 6 detik, atau server Anda tidak aktif selama 2 menit, peristiwa tersebut hilang. Hanya 2xx yang dianggap berhasil dikirim; 3xx, 4xx, dan 5xx dianggap gagal.
Jadi, alur penanganan permintaan hanya melakukan tiga hal: memverifikasi, menulis body mentah ke antrean atau tabel dan menunggu commit, lalu mengembalikan 200. Pengayaan data, panggilan kembali ke REST API, dan segala hal yang melibatkan pihak ketiga dijalankan dari antrean. Setiap peristiwa kecuali listing.submission memiliki struktur payload yang sama:
{ "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" }}location_id berupa string numerik, kecuali pada peristiwa interaction.*, yang meneruskannya tanpa normalisasi dan nilainya dapat berupa null. listing.submission tidak memiliki timestamp atau account_id dan menempatkan status serta live_link di tingkat teratas. Field opsional dalam data dihilangkan alih-alih dikirim sebagai null. Kembalikan 200 untuk setiap event yang tidak Anda kenali; kelompok peristiwa yang tidak didokumentasikan menggunakan URL yang sama.
5. Hilangkan duplikat dan tangani pengiriman yang tidak berurutan
Anda dapat menerima event yang sama dua kali, dan Anda akan menerima id sumber daya yang sama berkali-kali: interaction.review dipicu kembali dengan id interaksi yang sama setiap kali ulasan berubah, dan tidak ada payload yang memuat versi. Jadi, jangan pernah mengabaikan event berdasarkan kombinasi id dan nama event, karena Anda akan melewatkan perubahan yang sebenarnya. Deteksi duplikat yang persis sama dengan menghitung hash dari isi mentah permintaan dan mengabaikan pengulangannya dalam rentang waktu singkat, lalu gunakan id sumber daya sebagai kunci operasi penulisan agar pengulangan menghasilkan hasil yang sama.
import hashlibimport time SEEN: dict[str, float] = {} # use Redis with a TTL in productionWINDOW_SECONDS = 600 def is_duplicate(raw_body: bytes) -> bool: key = hashlib.sha256(raw_body).hexdigest() now = time.time() for k, seen_at in list(SEEN.items()): if now - seen_at > WINDOW_SECONDS: del SEEN[k] if key in SEEN: return True SEEN[key] = now return FalseUrutan juga tidak dijamin. Event berasal dari pipeline yang independen, sehingga local_post.published dapat tiba sebelum local_post.created, dan timestamp diberi cap waktu saat pengiriman disiapkan, bukan saat perubahan terjadi. Jika urutan penting, baca kembali sumber daya dari REST API dan andalkan hasil pembacaan tersebut. profile.updated juga dibatasi melalui debounce menjadi satu event per lokasi per 60 detik, sehingga pembacaan ulang saat event tiba hanya melihat perubahan pertama; baca lagi setelah rentang waktu tersebut berakhir.
6. Petakan event ke panduan lainnya
Buat percabangan berdasarkan event dan teruskan setiap event ke kode yang Anda tulis untuk panduan yang sesuai. Webhook memberi tahu kapan sesuatu terjadi; endpoint dalam panduan tersebut memberi tahu apa yang terjadi.
Tabel field dan contoh payload untuk setiap event tersedia dalam referensi event.
7. Pulihkan event yang terlewat dengan polling
Karena tidak ada percobaan ulang, setiap kelompok memerlukan pembacaan data otoritatif yang dapat Anda andalkan sebagai cadangan. Lakukan rekonsiliasi secara terjadwal, bukan hanya saat ada sesuatu yang tampak salah, dan jangan pernah menulis nilai nol atau "tidak ada perubahan" hanya karena sebuah event tidak tiba.
Dasbor mencatat setiap upaya beserta statusnya. success berarti berhasil dikirim; kode HTTP, timeout, network_error, atau error berarti endpoint Anda berhasil dijangkau tetapi gagal memproses pengiriman; blocked_url, endpoint_unverified, dan rate_limited berarti tidak ada permintaan yang dibuat sama sekali, sehingga perbaikannya ada pada konfigurasi atau paket langganan, bukan kode. Daftar lengkapnya ada di Perilaku pengiriman.
Langkah berikutnya
- Ambil ulasan: kelompok pertama yang diterapkan oleh sebagian besar integrasi, dengan penyisiran rollup sebagai mekanisme cadangannya.
- Periksa status profil bisnis dan tautan aktif: hal yang diumumkan oleh
listing.submission. - Referensi: Ringkasan webhook, Tanda tangan dan verifikasi, Perilaku pengiriman, Referensi peristiwa.