Calendly
Calendly adalah tool booking online yang banyak digunakan, tetapi fitur pengingat SMS bawaannya hanya mendukung sebagian negara dan wilayah, dengan cakupan terbatas di pasar Asia Pasifik, Asia Tenggara, Timur Tengah, dan lainnya. Dengan menggabungkan Calendly Webhook dan EngageLab SMS, Anda dapat mengirim notifikasi SMS ke nomor mana pun di seluruh dunia saat booking dibuat, dibatalkan, serta sebelum meeting dimulai, untuk menutup kekosongan geografis dari kemampuan native Calendly.
Prasyarat
Sebelum memulai, harap pastikan konfigurasi berikut sudah selesai:
Sisi EngageLab
- Sudah mengaktifkan layanan EngageLab SMS
- Sudah membuat template SMS di halaman pengelolaan template dan lolos peninjauan, serta mendapatkan ID template
- Sudah membuat secret key API di halaman secret key API, serta mendapatkan
dev_keydandev_secret
Sisi Calendly
- Sudah memiliki paket Calendly Standard atau lebih tinggi (fitur Webhook memerlukan paket berbayar)
- Sudah membuat Personal Access Token di halaman Integrations & apps → API and webhooks
Sisi Server
- Memiliki server yang dapat diakses publik, dan telah dikonfigurasi dengan sertifikat HTTPS yang valid
- Saat pengembangan dan debugging lokal, dapat menggunakan ngrok untuk mengekspos port lokal sementara
Langkah Pertama: Menyiapkan Template SMS
Memanggil API untuk mengirim SMS harus menggunakan template yang sudah lolos peninjauan, tidak mendukung input teks kustom secara langsung.
Login ke konsol EngageLab, masuk ke SMS → Pengelolaan Template, lalu buat tiga template berikut sesuai skenario:
| Kegunaan Template | Contoh Konten Template |
|---|---|
| Konfirmasi booking | Halo {{name}}, meeting Anda telah dikonfirmasi, waktu: {{time}}, kami menantikan kehadiran Anda. |
| Notifikasi pembatalan | Halo {{name}}, booking meeting Anda telah dibatalkan, jika ingin booking ulang silakan hubungi kami. |
| Pengingat meeting | Halo {{name}}, Anda memiliki meeting yang akan dimulai dalam {{advance}}, waktu: {{time}}, harap bersiap lebih awal. |
Setelah template diajukan, tunggu hingga lolos peninjauan, lalu catat ketiga ID template tersebut.
Saran: Buat template terpisah untuk setiap skenario agar maknanya lebih jelas dan tingkat kelulusan peninjauan lebih tinggi. Jika template mengandung variabel kustom (seperti
{{name}}), saat memanggil API perlu memberikan nilai melalui fieldparams, jika tidak variabel akan dikirim apa adanya.
Langkah Kedua: Membuat Langganan Webhook di Calendly
Pengelolaan Webhook Calendly tidak memiliki antarmuka visual, perlu dibuat melalui API.
Mendapatkan URI Organisasi
Pertama panggil endpoint berikut untuk mendapatkan URI organisasi akun saat ini:
curl https://api.calendly.com/users/me \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
Pada hasil yang dikembalikan, temukan field current_organization, dengan format sebagai berikut:
https://api.calendly.com/organizations/xxxxxxxxxxxxxxxx
Membuat Langganan Webhook
Gunakan URI organisasi untuk membuat Webhook, berlangganan dua event yaitu pembuatan dan pembatalan booking:
curl -X POST https://api.calendly.com/webhook_subscriptions \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://alamat-server-anda/webhooks/calendly",
"events": [
"invitee.created",
"invitee.canceled"
],
"organization": "https://api.calendly.com/organizations/ID-organisasi-anda",
"scope": "organization"
}'
Setelah berhasil dibuat, Calendly akan mengirim satu request verifikasi ke url yang diisi, dan server harus mengembalikan HTTP 200 untuk menyelesaikan handshake. Selanjutnya setiap kali ada orang yang booking atau membatalkan, event akan dikirim ke alamat tersebut.
Catatan:
urlharus berupa alamat HTTPS yang dapat diakses publik. Saat pengembangan lokal, dapat menggunakan ngrok untuk menghasilkan alamat sementara:ngrok http 3000, lalu isikanhttps://xxxx.ngrok.ioyang dihasilkan.
Langkah Ketiga: Membangun Layanan Penerima Webhook
Berikut adalah contoh lengkap server Node.js, yang menerima event Calendly Webhook lalu memanggil EngageLab SMS API untuk mengirim SMS.
Menginstal Dependensi
npm install express
Kode Lengkap
// server.js
import express from 'express';
const app = express();
app.use(express.json());
// Autentikasi EngageLab: base64(dev_key:dev_secret)
const ENGAGELAB_AUTH = Buffer.from(
`${process.env.ENGAGELAB_DEV_KEY}:${process.env.ENGAGELAB_DEV_SECRET}`
).toString('base64');
// Memanggil EngageLab SMS API untuk mengirim SMS
async function sendSMS({ to, templateId, params }) {
const res = await fetch('https://smsapi.engagelab.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${ENGAGELAB_AUTH}`,
},
body: JSON.stringify({
to: [to],
template: {
id: templateId,
params,
},
}),
});
const data = await res.json();
// HTTP 200 tidak berarti pengiriman berhasil, perlu memeriksa field code
if (data.code && data.code !== 0) {
console.error(`SMS gagal dikirim: code=${data.code}, message=${data.message}`);
} else {
console.log(`SMS berhasil dikirim: plan_id=${data.plan_id}, message_id=${data.message_id}`);
}
}
// Mengonversi string waktu ISO menjadi waktu lokal (Asia/Shanghai)
function formatTime(isoString) {
return new Date(isoString).toLocaleString('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
// Mendaftarkan tugas terjadwal pengingat sebelum meeting (24 jam dan 1 jam masing-masing satu)
function scheduleReminders({ phone, name, startTime }) {
const meetingTime = new Date(startTime).getTime();
const now = Date.now();
const reminders = [
{ advance: '24 jam', triggerAt: meetingTime - 24 * 60 * 60 * 1000 },
{ advance: '1 jam', triggerAt: meetingTime - 60 * 60 * 1000 },
];
for (const { advance, triggerAt } of reminders) {
const delay = triggerAt - now;
if (delay <= 0) continue; // Waktu trigger sudah lewat, lewati
setTimeout(async () => {
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_REMINDER,
params: { name, time: formatTime(startTime), advance },
});
}, delay);
}
}
// Endpoint penerima Webhook
app.post('/webhooks/calendly', async (req, res) => {
const { event, payload } = req.body;
const phone = payload.text_reminder_number; // Nomor ponsel yang diisi pengguna saat booking
const name = payload.invitee?.name ?? 'Pengguna';
const startTime = payload.scheduled_event?.start_time;
// Lewati pengiriman SMS jika nomor ponsel tidak diisi
if (!phone) {
console.log('Booking ini tidak mengisi nomor ponsel, lewati notifikasi SMS');
return res.sendStatus(200);
}
if (event === 'invitee.created') {
// Mengirim SMS konfirmasi booking
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CONFIRM,
params: { name, time: formatTime(startTime) },
});
// Mendaftarkan tugas pengingat sebelum meeting
scheduleReminders({ phone, name, startTime });
} else if (event === 'invitee.canceled') {
// Mengirim SMS notifikasi pembatalan
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CANCEL,
params: { name },
});
}
// Harus mengembalikan 200 dalam 2 detik, jika tidak Calendly akan menganggap pengiriman gagal dan mencoba ulang
res.sendStatus(200);
});
app.listen(3000, () => console.log('Server running on port 3000'));
Konfigurasi Environment Variable
Buat file .env di direktori root proyek, lalu isikan variabel berikut:
ENGAGELAB_DEV_KEY=dev_key_anda
ENGAGELAB_DEV_SECRET=dev_secret_anda
TEMPLATE_ID_CONFIRM=ID_template_konfirmasi_booking
TEMPLATE_ID_CANCEL=ID_template_notifikasi_pembatalan
TEMPLATE_ID_REMINDER=ID_template_pengingat_meeting
Penjelasan Field Penting
Pada payload yang dikirim Calendly Webhook, field-field berikut berkaitan langsung dengan pengiriman SMS:
| Field | Penjelasan |
|---|---|
event |
Tipe event, invitee.created (booking dibuat) atau invitee.canceled (booking dibatalkan) |
payload.text_reminder_number |
Nomor ponsel yang diisi pemesan, termasuk kode negara, bisa kosong |
payload.invitee.name |
Nama pemesan |
payload.scheduled_event.start_time |
Waktu mulai meeting, format ISO 8601 |
Skenario Lanjutan
Mengirim SMS Tindak Lanjut Setelah Meeting Selesai
Tambahkan satu tugas yang terpicu setelah meeting selesai pada scheduleReminders, untuk mengirim survei kepuasan atau undangan booking berikutnya:
// Tambahkan pada fungsi scheduleReminders
const followUpAt = meetingTime + 30 * 60 * 1000; // 30 menit setelah meeting selesai
const followUpDelay = followUpAt - now;
if (followUpDelay > 0) {
setTimeout(async () => {
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_FOLLOWUP,
params: { name },
});
}, followUpDelay);
}
Memberi Tahu Host Sekaligus
Saat booking dibuat, selain memberi tahu pemesan, Anda juga dapat mengirim pengingat ke host meeting:
if (event === 'invitee.created') {
// Memberi tahu pemesan
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CONFIRM,
params: { name, time: formatTime(startTime) },
});
// Memberi tahu host
await sendSMS({
to: process.env.HOST_PHONE,
templateId: process.env.TEMPLATE_ID_HOST_NOTIFY,
params: { name, time: formatTime(startTime) },
});
}
Catatan Penting
- Webhook harus merespons
HTTP 200dalam 2 detik, jika tidak Calendly akan menganggap pengiriman gagal dan mencoba ulang. Disarankan untuk mengembalikan 200 terlebih dahulu, baru menjalankan logika pengiriman SMS secara asinkron, guna menghindari kesalahan penilaian Calendly akibat keterlambatan respons EngageLab API. - Field
text_reminder_numberbisa kosong, perlu penanganan null dalam kode, agar tidak meneruskan nomor kosong ke SMS API yang menyebabkan error. - Format nomor ponsel harus menyertakan kode negara, misalnya nomor Singapura adalah
+6591234567. Calendly akan mengarahkan pengguna untuk mengisi format internasional saat mengumpulkan nomor ponsel, tetapi disarankan untuk melakukan validasi format sekali lagi di sisi server. setTimeouttidak cocok untuk lingkungan produksi, semua tugas terjadwal akan hilang setelah server restart. Untuk lingkungan produksi disarankan menggunakan antrian tugas persisten (seperti BullMQ + Redis) atau tugas terjadwal layanan cloud, simpan catatan pengingat ke database agar dapat dipulihkan setelah restart.- Template harus lolos peninjauan sebelum dapat digunakan, jika saat dipanggil template berstatus menunggu peninjauan atau ditolak, API akan mengembalikan error
4001. - API mengembalikan HTTP 200 tidak berarti SMS berhasil dikirim, harap periksa field
codepada response body, jika tidak nol lihat penjelasan kode error untuk menyelidiki penyebabnya.










