Webhook booking yang idempotent: cara menerima booking.created tanpa proses ganda
Tim Termilo · Diterbitkan 12 September 2026 · 6 menit baca

Catatan
Webhook Termilo bersifat at-least-once, jadi event yang sama dapat tiba dua kali. Verifikasi tanda tangan dari body mentah, simpan id event sebelum memproses, abaikan id yang sudah ada, dan balas 2xx hanya setelah tersimpan. Pola ini membuat retry dan kirim ulang manual aman.
Kenapa webhook bisa tiba dua kali?
Termilo menyimpan setiap perubahan booking sebagai satu event, lalu mengirimkannya ke setiap endpoint aktif. Kiriman dihitung berhasil hanya bila endpoint Anda membalas 2xx. Balasan lain, batas waktu habis, atau gangguan jaringan memicu percobaan ulang: sampai enam percobaan, dengan jeda mulai satu menit lalu berlipat. Handler yang sudah memproses event tetapi gagal membalas 2xx menerima event itu lagi.
- Retry otomatis: percobaan berikutnya membawa id, type, dan body yang sama persis; hanya tanda tangan dan timestamp yang baru.
- Kirim ulang manual: admin menekan Kirim ulang dari Riwayat pengiriman di dashboard setelah endpoint Anda pulih.
- Persetujuan otomatis: booking yang langsung terkonfirmasi mengirim booking.created dan booking.confirmed dengan id yang sama; keduanya hanya berbeda pada type.
- Urutan tiba tidak dijamin: retry booking.created dapat tiba setelah booking.confirmed untuk booking yang sama.
Ini bukan kekhususan Termilo. Dokumentasi webhook GitHub menyarankan penerima mengenali kiriman ulang lewat header delivery ID dan membalas 2xx dalam 10 detik (praktik terbaik webhook GitHub). Spesifikasi terbuka Standard Webhooks memakai header webhook-id sebagai kunci idempotensi (spesifikasi Standard Webhooks).
Bagaimana memverifikasi tanda tangan dari body mentah?
Setiap kiriman membawa empat header: X-Termilo-Signature, X-Termilo-Timestamp, X-Termilo-Event-Id, dan X-Termilo-Event-Type. Tanda tangan adalah HMAC-SHA256 atas string timestamp, tanda titik, lalu body mentah, memakai signing secret endpoint Anda. Verifikasi dulu, baru parse JSON.
- Baca body sebagai byte mentah persis seperti diterima. Jangan parse lalu serialize ulang; urutan kunci atau spasi yang berubah mengubah hasil HMAC.
- Ambil nilai X-Termilo-Timestamp (UTC epoch detik), lalu bentuk string: timestamp, titik, body mentah.
- Hitung HMAC-SHA256 atas string itu dengan signing secret endpoint. Tulis hasilnya sebagai sha256= diikuti hex huruf kecil.
- Bandingkan hasil Anda dengan X-Termilo-Signature memakai perbandingan waktu-konstan: timingSafeEqual di Node.js atau hmac.compare_digest di Python.
- Tolak kiriman yang tanda tangannya tidak cocok, atau yang timestamp-nya terlalu jauh dari jam server Anda; batas toleransinya Anda tentukan sendiri.
Fungsi verifikasi lengkap untuk Node.js dan Python ada di dokumentasi webhook; salin apa adanya. Signing secret berbeda dengan API key: API key dipakai backend Anda untuk memanggil API v1, signing secret dipakai untuk memeriksa kiriman dari Termilo. Keduanya hanya ditampilkan sekali (autentikasi). Bila signing secret hilang, buat endpoint baru, lalu hapus endpoint lama.
Bagaimana menyimpan id event dan membuat handler idempotent?
Kunci idempotensi Termilo adalah pasangan id dan type. Nilai id di body sama dengan header X-Termilo-Event-Id; nilai type sama dengan X-Termilo-Event-Type. Simpan pasangan itu dengan batasan unik sebelum Anda menjalankan efek samping apa pun. Handler yang benar mengikuti urutan ini:
- Verifikasi tanda tangan seperti di atas. Bila gagal, balas 401; jangan simpan apa pun.
- Parse JSON, lalu baca id dan type. Sisipkan pasangan itu ke tabel event yang sudah diproses, dengan batasan unik pada kedua kolom.
- Bila sisipan ditolak karena pasangan sudah ada, balas 200 tanpa memproses ulang. Inilah jalur untuk retry dan kirim ulang.
- Bila sisipan berhasil, jalankan efek samping dalam transaksi yang sama, atau taruh pekerjaan berat di antrean Anda sendiri. Bila efek samping gagal, batalkan transaksi termasuk baris id event, lalu balas 500 agar Termilo mengirim lagi.
- Balas 2xx hanya setelah id event tersimpan permanen. Balasan 2xx sebelum simpan berarti event hilang bila proses Anda mati di tengah jalan.
Perhatian
Simpan id bersama type, bukan id saja. Booking yang disetujui otomatis mengirim booking.created dan booking.confirmed dengan id yang sama; handler yang hanya menyimpan id membuang event confirmed, dan CRM Anda tidak pernah tahu booking itu terkonfirmasi.
Dua pengaman tambahan menjaga handler saat event ganda lolos. Pertama, pakai bookingId dari data.booking sebagai kunci upsert, sehingga memproses event yang sama dua kali menghasilkan keadaan akhir yang sama. Kedua, bandingkan occurredAtMs dengan nilai tersimpan sebelum menimpa status, karena urutan tiba tidak dijamin.
Bagaimana memetakan tujuh event ke contact, activity, atau deal?
Semua event memakai amplop yang sama: id, type, occurredAtMs, dan data berisi booking, service, dan customer. Data booking membawa bookingId, status, startsAtMs, endsAtMs, dan timezone; data customer membawa nama dan email. Tabel berikut memetakan tiap event ke tindakan CRM yang aman diulang.
| Event | Kapan dikirim | Tindakan di CRM, upsert per bookingId |
|---|---|---|
| booking.created | Booking baru dibuat; status held, atau confirmed bila disetujui otomatis | Upsert contact dari nama dan email; buat activity booking bila tidak ada |
| booking.confirmed | Booking disetujui, otomatis atau manual | Set activity ke terkonfirmasi; buka deal bila alur penjualan Anda memakainya |
| booking.rescheduled | Waktu booking dipindahkan | Perbarui waktu activity yang ada; jangan buat activity kedua |
| booking.cancelled | Booking held atau confirmed dibatalkan | Tandai activity batal; tutup deal yang terbuka |
| booking.completed | Admin menandai selesai | Tandai activity selesai; picu follow-up |
| booking.no_show | Pelanggan tidak datang | Tandai activity tidak hadir; tambah catatan risiko di contact |
| booking.expired | Hold berakhir: 10 menit menunggu deposit, 24 jam menunggu persetujuan manual | Tandai activity kedaluwarsa; simpan contact untuk follow-up |
Saring event dari nilai type; endpoint yang dibuat dari dashboard menerima semua event. Balas 2xx untuk type yang tidak Anda pakai. Transisi status lengkap ada di lifecycle booking. Termilo tetap sistem booking; pipeline penjualan tetap milik CRM Anda. Konteks lengkap untuk owner SaaS ada di halaman software booking untuk SaaS.
Bagaimana menguji retry dan kirim ulang dari dashboard?
Uji jalur gagal sebelum produksi. Buka Admin → Webhooks di dashboard Termilo, klik Tambah endpoint, isi URL endpoint (harus HTTPS) dan Deskripsi, lalu klik Simpan endpoint. Salin signing secret dari dialog yang muncul; secret itu tidak ditampilkan lagi.
- Buat satu booking percobaan dari halaman booking Anda. Buka Riwayat pengiriman: baris booking.created tampil dengan status respons dan jumlah percobaan.
- Atur handler agar membalas 500 untuk satu kiriman. Riwayat percobaan di detail delivery mencatat percobaan berikutnya dengan jeda berlipat.
- Kembalikan handler ke normal, lalu klik Kirim ulang pada delivery yang gagal. Handler harus membalas 200 tanpa membuat activity kedua, karena pasangan id dan type sudah tersimpan.
- Periksa CRM: satu contact, satu activity, status terakhir benar. Activity ganda berarti kunci unik salah, atau 2xx dibalas sebelum simpan.
Setelah enam percobaan gagal, delivery berstatus gagal sampai Anda mengirim ulang manual; riwayat percobaan tetap tersimpan. Agen AI yang terhubung lewat MCP dapat membuat endpoint, mengirim event uji, dan membaca riwayat pengiriman (MCP untuk AI).
Apa yang tidak dikirim webhook: event pembayaran
Webhook Termilo hanya mengirim tujuh event booking. Tidak ada event pembayaran atau deposit. Booking yang meminta deposit lahir sebagai booking.created dengan status held; perubahan berikutnya tiba sebagai booking.confirmed atau booking.expired. Nominal dan status pembayaran dibaca dari akun gateway pembayaran milik bisnis.
- Webhook masuk dari CRM ke Termilo tidak tersedia; perubahan dari sisi CRM dilakukan lewat API v1 (referensi API).
- Body event memuat nama dan email pelanggan, bukan nomor telepon; ambil detail pelanggan lewat API v1.
- Retry dan kirim ulang memakai body kiriman pertama, bukan data terbaru. Untuk keadaan terkini, baca booking lewat API v1.
Klinik, salon, dan bengkel memakai pola yang sama untuk kasir atau CRM; lihat hub software booking.
Dokumentasi terkait
Sumber
Baca juga
Artikel
Vertikal