Calendly
Calendly est un outil de planification en ligne très répandu, mais sa fonctionnalité native de rappel par SMS ne couvre qu’un nombre limité de pays et régions, avec une couverture limitée en Asie-Pacifique, en Asie du Sud-Est, au Moyen-Orient et sur d’autres marchés. En intégrant les webhooks Calendly avec EngageLab SMS, vous pouvez envoyer des notifications SMS vers n’importe quel numéro dans le monde lorsqu’un rendez-vous est créé ou annulé, ou avant le début d’une réunion — comblant ainsi les lacunes géographiques des capacités natives de Calendly.
Prérequis
Avant de commencer, assurez-vous que les configurations suivantes sont en place :
Côté EngageLab
- Le service SMS EngageLab est activé
- Un modèle SMS a été créé et approuvé sur la page Gestion des modèles ; l’ID du modèle a été obtenu
- Une clé API a été créée sur la page Clés API ;
dev_keyetdev_secretont été obtenus
Côté Calendly
- Vous disposez d’un abonnement Calendly Standard ou supérieur (les webhooks nécessitent un abonnement payant)
- Un jeton d’accès personnel a été créé sur la page Integrations & apps → API and webhooks
Côté serveur
- Vous disposez d’un serveur accessible depuis Internet avec un certificat HTTPS valide
- Pour le développement et le débogage locaux, vous pouvez utiliser ngrok pour exposer temporairement un port local
Étape 1 : Préparer les modèles SMS
L’envoi de SMS via l’API nécessite des modèles préapprouvés ; le texte personnalisé ne peut pas être envoyé directement.
Connectez-vous à la console EngageLab, allez dans SMS → Gestion des modèles, et créez les trois modèles suivants pour chaque scénario :
| Objectif du modèle | Exemple de contenu |
|---|---|
| Confirmation de rendez-vous | Hi {{name}}, your meeting has been confirmed for {{time}}. Looking forward to seeing you. |
| Avis d’annulation | Hi {{name}}, your meeting appointment has been canceled. Please contact us to reschedule. |
| Rappel de réunion | Hi {{name}}, you have a meeting starting in {{advance}}, scheduled for {{time}}. Please prepare in advance. |
Après soumission des modèles, attendez l’approbation et notez les trois ID de modèle.
Astuce : Créez un modèle distinct pour chaque scénario — l’intention est plus claire et le taux d’approbation s’en trouve amélioré. Si un modèle contient des variables personnalisées (ex.
{{name}}), vous devez transmettre les valeurs via le champparamslors de l’appel API ; sinon, les variables seront envoyées telles quelles.
Étape 2 : Créer un abonnement webhook dans Calendly
Calendly ne fournit pas d’interface visuelle pour la gestion des webhooks ; les abonnements doivent être créés via l’API.
Obtenir l’URI de l’organisation
Appelez d’abord le point de terminaison suivant pour obtenir l’URI de l’organisation de votre compte :
curl https://api.calendly.com/users/me \
-H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
Repérez le champ current_organization dans la réponse, qui ressemble à :
https://api.calendly.com/organizations/xxxxxxxxxxxxxxxx
Créer l’abonnement webhook
Utilisez l’URI de l’organisation pour créer un webhook qui s’abonne aux événements de création et d’annulation de rendez-vous :
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"
}'
Une fois créé, Calendly enverra une requête de vérification vers l’url fournie, et votre serveur doit renvoyer HTTP 200 pour finaliser la poignée de main. Les événements de rendez-vous et d’annulation suivants seront poussés vers cette adresse.
Remarque : L’
urldoit être une adresse HTTPS accessible publiquement. Pour le développement local, vous pouvez utiliser ngrok pour générer une adresse temporaire :ngrok http 3000, puis utiliser l’URLhttps://xxxx.ngrok.ioaffichée.
Étape 3 : Mettre en place le service récepteur de webhook
Voici un exemple complet de serveur Node.js qui reçoit les événements webhook Calendly et appelle l’API SMS EngageLab pour envoyer les messages.
Installer les dépendances
npm install express
Code complet
// server.js
import express from 'express';
const app = express();
app.use(express.json());
// EngageLab authentication: base64(dev_key:dev_secret)
const ENGAGELAB_AUTH = Buffer.from(
`${process.env.ENGAGELAB_DEV_KEY}:${process.env.ENGAGELAB_DEV_SECRET}`
).toString('base64');
// Call EngageLab SMS API to send a message
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 does not guarantee delivery; check the code field
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}`);
}
}
// Convert an ISO time string to local time (Asia/Shanghai)
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',
});
}
// Schedule pre-meeting reminders (24h and 1h before)
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; // Trigger time has passed, skip
setTimeout(async () => {
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_REMINDER,
params: { name, time: formatTime(startTime), advance },
});
}, delay);
}
}
// Webhook receiver endpoint
app.post('/webhooks/calendly', async (req, res) => {
const { event, payload } = req.body;
const phone = payload.text_reminder_number; // Phone number provided by the invitee
const name = payload.invitee?.name ?? 'User';
const startTime = payload.scheduled_event?.start_time;
// Skip SMS if no phone number is provided
if (!phone) {
console.log('No phone number provided for this appointment, skipping SMS notification');
return res.sendStatus(200);
}
if (event === 'invitee.created') {
// Send appointment confirmation SMS
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CONFIRM,
params: { name, time: formatTime(startTime) },
});
// Schedule pre-meeting reminders
scheduleReminders({ phone, name, startTime });
} else if (event === 'invitee.canceled') {
// Send cancellation notice SMS
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CANCEL,
params: { name },
});
}
// Must return 200 within 2 seconds, otherwise Calendly will consider the push failed and retry
res.sendStatus(200);
});
app.listen(3000, () => console.log('Server running on port 3000'));
Configuration des variables d’environnement
Créez un fichier .env à la racine du projet avec les variables suivantes :
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
Référence des champs clés
Les champs suivants dans la charge utile du webhook Calendly sont directement liés à l’envoi de SMS :
| Champ | Description |
|---|---|
event |
Type d’événement : invitee.created (rendez-vous créé) ou invitee.canceled (rendez-vous annulé) |
payload.text_reminder_number |
Numéro fourni par l’invité, avec indicatif pays ; peut être vide |
payload.invitee.name |
Nom de l’invité |
payload.scheduled_event.start_time |
Heure de début de la réunion au format ISO 8601 |
Scénarios étendus
Envoyer un SMS de suivi après une réunion
Ajoutez un déclencheur post-réunion dans scheduleReminders pour envoyer une enquête de satisfaction ou une invitation à un nouveau rendez-vous :
// Append to the scheduleReminders function
const followUpAt = meetingTime + 30 * 60 * 1000; // 30 minutes after the meeting ends
const followUpDelay = followUpAt - now;
if (followUpDelay > 0) {
setTimeout(async () => {
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_FOLLOWUP,
params: { name },
});
}, followUpDelay);
}
Avertir également l’hôte
Lorsqu’un rendez-vous est créé, en plus de prévenir l’invité, vous pouvez envoyer un rappel à l’hôte de la réunion :
if (event === 'invitee.created') {
// Notify the invitee
await sendSMS({
to: phone,
templateId: process.env.TEMPLATE_ID_CONFIRM,
params: { name, time: formatTime(startTime) },
});
// Notify the host
await sendSMS({
to: process.env.HOST_PHONE,
templateId: process.env.TEMPLATE_ID_HOST_NOTIFY,
params: { name, time: formatTime(startTime) },
});
}
Points importants
- Les webhooks doivent répondre par
HTTP 200dans les 2 secondes, sinon Calendly considère que la livraison a échoué et réessaie. Il est recommandé de renvoyer 200 d’abord, puis d’exécuter l’envoi de SMS de manière asynchrone pour éviter qu’une latence de l’API EngageLab ne fasse échouer à tort la livraison côté Calendly. - Le champ
text_reminder_numberpeut être vide — ajoutez des vérifications de nullité dans votre code pour ne pas transmettre un numéro vide à l’API SMS, ce qui provoquerait des erreurs. - Les numéros doivent inclure l’indicatif pays, ex.
+8618701235678pour la Chine continentale. Calendly invite les utilisateurs au format international lors de la saisie du téléphone, mais une validation côté serveur est recommandée. setTimeoutne convient pas aux environnements de production — toutes les tâches planifiées sont perdues au redémarrage du serveur. En production, utilisez une file de tâches persistante (par ex. BullMQ + Redis) ou des tâches planifiées cloud, et stockez les rappels en base pour pouvoir les reprendre après redémarrage.- Les modèles doivent être approuvés avant utilisation — si un modèle est en attente d’examen ou rejeté lors de l’appel, l’API renvoie une erreur
4001. - Une réponse HTTP 200 de l’API ne garantit pas la livraison du SMS — vérifiez le champ
codedans le corps de la réponse. Consultez la documentation des codes d’erreur pour les valeurs non nulles.
