Calendly
Calendly ist ein weit verbreitetes Online-Terminbuchungstool, dessen integrierte SMS-Erinnerung jedoch nur in einer begrenzten Zahl von Ländern und Regionen verfügbar ist — mit eingeschränkter Abdeckung in Asien-Pazifik, Südostasien, dem Nahen Osten und anderen Märkten. Durch die Anbindung von Calendly-Webhooks an EngageLab SMS können Sie bei Terminerstellung, -absage oder vor Beginn eines Meetings SMS-Benachrichtigungen an Rufnummern weltweit senden und so die geografischen Lücken der nativen Calendly-Funktionen schließen.
Voraussetzungen
Bevor Sie starten, sollten folgende Konfigurationen abgeschlossen sein:
EngageLab-Seite
- Der EngageLab-SMS-Dienst ist aktiviert
- Eine SMS-Vorlage wurde auf der Seite „Vorlagenverwaltung“ erstellt und freigegeben; die Vorlagen-ID liegt vor
- Auf der Seite „API-Schlüssel“ wurde ein API-Schlüssel erstellt;
dev_keyunddev_secretliegen vor
Calendly-Seite
- Sie verfügen über einen Calendly-Standard-Tarif oder höher (Webhooks erfordern einen kostenpflichtigen Plan)
- Auf der Seite Integrationen & Apps → API und Webhooks wurde ein persönliches Zugriffstoken erstellt
Server-Seite
- Sie betreiben einen Server mit öffentlichem Internetzugang und gültigem HTTPS-Zertifikat
- Für lokale Entwicklung und Fehlersuche können Sie mit ngrok vorübergehend einen lokalen Port freigeben
Schritt 1: SMS-Vorlagen vorbereiten
Der Versand per API setzt freigegebene Vorlagen voraus; freier Fließtext kann nicht direkt versendet werden.
Melden Sie sich in der EngageLab-Konsole an, öffnen Sie SMS → Vorlagenverwaltung und legen Sie für jedes Szenario die folgenden drei Vorlagen an:
| Zweck der Vorlage | Beispielinhalt |
|---|---|
| Terminbestätigung | Hi {{name}}, your meeting has been confirmed for {{time}}. Looking forward to seeing you. |
| Absagehinweis | Hi {{name}}, your meeting appointment has been canceled. Please contact us to reschedule. |
| Terminerinnerung | Hi {{name}}, you have a meeting starting in {{advance}}, scheduled for {{time}}. Please prepare in advance. |
Nach dem Einreichen warten Sie auf die Freigabe und notieren sich die drei Vorlagen-IDs.
Hinweis: Legen Sie pro Szenario eine eigene Vorlage an — das macht die Absicht klarer und verbessert die Freigabequote. Enthält eine Vorlage eigene Variablen (z. B.
{{name}}), müssen die Werte beim API-Aufruf im Feldparamsübergeben werden; andernfalls werden die Platzhalter unverändert mitgesendet.
Schritt 2: Webhook-Abonnement in Calendly anlegen
Calendly bietet keine grafische Oberfläche zur Webhook-Verwaltung; Abonnements müssen über die API erstellt werden.
Organisations-URI abrufen
Rufen Sie zunächst folgenden Endpunkt auf, um die Organisations-URI Ihres Kontos zu erhalten:
curl https://api.calendly.com/users/me \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
Suchen Sie in der Antwort das Feld current_organization, etwa in dieser Form:
https://api.calendly.com/organizations/xxxxxxxxxxxxxxxx
Webhook-Abonnement erstellen
Verwenden Sie die Organisations-URI, um einen Webhook für Terminerstellung und -absage anzulegen:
curl -X POST https://api.calendly.com/webhook_subscriptions \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server-address/webhooks/calendly",
"events": [
"invitee.created",
"invitee.canceled"
],
"organization": "https://api.calendly.com/organizations/your-org-id",
"scope": "organization"
}'
Nach der Erstellung sendet Calendly eine Verifizierungsanfrage an die angegebene url; Ihr Server muss mit HTTP 200 antworten, um den Handshake abzuschließen. Anschließend werden Termin- und Absageereignisse an diese Adresse übermittelt.
Hinweis: Die
urlmuss öffentlich per HTTPS erreichbar sein. Für die lokale Entwicklung können Sie mit ngrok eine temporäre Adresse erzeugen:ngrok http 3000, dann die ausgegebenehttps://xxxx.ngrok.io-URL verwenden.
Schritt 3: Webhook-Empfangsdienst implementieren
Nachfolgend ein vollständiges Node.js-Beispiel, das Calendly-Webhook-Ereignisse entgegennimmt und die EngageLab-SMS-API zum Versand aufruft.
Abhängigkeiten installieren
npm install express
Vollständiger Code
// server.js
import express from 'express';
const app = express();
app.use(express.json());
const ENGAGELAB_AUTH = Buffer.from(
`${process.env.ENGAGELAB_DEV_KEY}:${process.env.ENGAGELAB_DEV_SECRET}`
).toString('base64');
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();
if (data.code && data.code !== 0) {
console.error(`SMS sending failed: code=${data.code}, message=${data.message}`);
} else {
console.log(`SMS sent successfully: plan_id=${data.plan_id}, message_id=${data.message_id}`);
}
}
function formatTime(isoString) {
return new Date(isoString).toLocaleString('en-US', {
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
});
}
function scheduleReminders({ phone, name, startTime }) {
const meetingTime = new Date(startTime).getTime();
const now = Date.now();
const reminders = [
{ advance: '24 hours', triggerAt: meetingTime - 24 * 60 * 60 * 1000 },
{ advance: '1 hour', triggerAt: meetingTime - 60 * 60 * 1000 },
];
for (const { advance, triggerAt } of reminders) {
const delay = triggerAt - now;
if (delay <= 0) continue;
setTimeout(async () => {
await sendSMS({ to: phone, templateId: process.env.TEMPLATE_ID_REMINDER, params: { name, time: formatTime(startTime), advance } });
}, delay);
}
}
app.post('/webhooks/calendly', async (req, res) => {
const { event, payload } = req.body;
const phone = payload.text_reminder_number;
const name = payload.invitee?.name ?? 'User';
const startTime = payload.scheduled_event?.start_time;
if (!phone) { console.log('No phone number provided, skipping'); return res.sendStatus(200); }
if (event === 'invitee.created') {
await sendSMS({ to: phone, templateId: process.env.TEMPLATE_ID_CONFIRM, params: { name, time: formatTime(startTime) } });
scheduleReminders({ phone, name, startTime });
} else if (event === 'invitee.canceled') {
await sendSMS({ to: phone, templateId: process.env.TEMPLATE_ID_CANCEL, params: { name } });
}
res.sendStatus(200);
});
app.listen(3000, () => console.log('Server running on port 3000'));
Umgebungsvariablen
Legen Sie im Projektstamm eine .env-Datei mit folgenden Variablen an:
ENGAGELAB_DEV_KEY=your_dev_key
ENGAGELAB_DEV_SECRET=your_dev_secret
TEMPLATE_ID_CONFIRM=appointment_confirmation_template_id
TEMPLATE_ID_CANCEL=cancellation_notice_template_id
TEMPLATE_ID_REMINDER=meeting_reminder_template_id
Referenz wichtiger Felder
| Feld | Beschreibung |
|---|---|
event |
Ereignistyp: invitee.created (Termin erstellt) oder invitee.canceled (Termin abgesagt) |
payload.text_reminder_number |
Vom Eingeladenen angegebene Rufnummer inkl. Landesvorwahl; kann leer sein |
payload.invitee.name |
Name des Eingeladenen |
payload.scheduled_event.start_time |
Beginn des Meetings im ISO-8601-Format |
Erweiterte Szenarien
Follow-up-SMS nach dem Meeting
Ergänzen Sie in scheduleReminders einen Trigger nach dem Meeting, um eine Zufriedenheitsumfrage oder eine Einladung zum Folgetermin zu senden:
const followUpAt = meetingTime + 30 * 60 * 1000;
const followUpDelay = followUpAt - now;
if (followUpDelay > 0) {
setTimeout(async () => {
await sendSMS({ to: phone, templateId: process.env.TEMPLATE_ID_FOLLOWUP, params: { name } });
}, followUpDelay);
}
Auch den Gastgeber benachrichtigen
if (event === 'invitee.created') {
await sendSMS({ to: phone, templateId: process.env.TEMPLATE_ID_CONFIRM, params: { name, time: formatTime(startTime) } });
await sendSMS({ to: process.env.HOST_PHONE, templateId: process.env.TEMPLATE_ID_HOST_NOTIFY, params: { name, time: formatTime(startTime) } });
}
Wichtige Hinweise
- Webhooks müssen innerhalb von 2 Sekunden mit
HTTP 200antworten, sonst gilt der Push für Calendly als fehlgeschlagen und wird wiederholt. - Das Feld
text_reminder_numberkann leer sein — bauen Sie entsprechende Prüfungen in Ihren Code ein. - Rufnummern müssen die Landesvorwahl enthalten, z. B.
+8618701235678. setTimeoutist für Produktionsumgebungen ungeeignet — verwenden Sie eine persistente Auftragswarteschlange (z. B. BullMQ + Redis).- Vorlagen müssen vor der Nutzung freigegeben sein — andernfalls liefert die API einen
4001-Fehler. - Eine HTTP-200-Antwort der API garantiert keinen erfolgreichen SMS-Zustellweg — prüfen Sie das Feld
code. Bei Werten ungleich null siehe die Dokumentation zu Fehlercodes.
