We build custom applications 5x faster and cheaper 🚀
Book a Free Consultation
Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.
Integrate Calendly with OpenClaw by: (1) choosing authentication (a user OAuth flow for multi-user installs or a Personal Access Token for a single account); (2) storing credentials and tokens in ClawHub as secrets; (3) running an external, hardened webhook receiver (a web service you control) that validates Calendly webhooks, normalizes events, and then explicitly invokes your OpenClaw skill (or enqueues work your skill will pick up); and (4) using Calendly REST APIs for lookups and webhooks while keeping state, retries, and long-running work outside the agent runtime. This makes the integration explicit, auditable, and safe — the agent never “magically” reads Calendly; your code does, with explicit credentials and verified webhooks.
<b>//</b> app.js - minimal webhook receiver with raw body parsing and HMAC verification
const express = require('express');
const crypto = require('crypto');
const app = express();
/<b>//</b> Use raw body for exact HMAC verification
app.use('/webhooks/calendly', express.raw({ type: 'application/json' }));
app.post('/webhooks/calendly', (req, res) => {
const rawBody = req.body; <b>//</b> Buffer
const secret = process.env.CALENDLY_WEBHOOK_SECRET; <b>//</b> set in ClawHub or env
const sigHeaderName = process.env.SIGNATURE_HEADER || 'x-calendly-signature'; <b>//</b> configurable
if (!secret) return res.status(500).send('Secret not configured');
<b>//</b> compute HMAC-SHA256; confirm algorithm with Calendly docs
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = (req.headers[sigHeaderName] || '').trim();
<b>//</b> constant-time compare
function safeEqual(a, b) {
try {
const bufA = Buffer.from(a, 'utf8');
const bufB = Buffer.from(b, 'utf8');
if (bufA.length !== bufB.length) return false;
return crypto.timingSafeEqual(bufA, bufB);
} catch (e) {
return false;
}
}
if (!safeEqual(expected, received)) {
return res.status(401).send('invalid signature');
}
<b>//</b> Parse JSON after signature verified
const payload = JSON.parse(rawBody.toString('utf8'));
<b>//</b> Normalize event and either call the OpenClaw skill or enqueue work
// Example: call your job API
// fetch('https://your.backend/jobs', { method: 'POST', body: JSON.stringify({ type: 'calendly.event', data: payload }), headers: { 'Content-Type': 'application/json' } });
res.status(200).send('ok');
});
app.listen(process.env.PORT || 3000);
<b>//</b> Create a webhook subscription - replace $TOKEN and the URL
curl -X POST "https://api.calendly.com/webhook_subscriptions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your.example/webhooks/calendly",
"events": ["invitee.created", "invitee.canceled"]
}'
<b>//</b> Fetch event details
curl -H "Authorization: Bearer $TOKEN" \
"https://api.calendly.com/scheduled_events/{event_uuid}"
Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.
1
The quick answer: confirm the webhook URL is publicly reachable and returns 200, ensure the webhook secret/signature verification matches Calendly’s docs, and confirm network/firewall/ingress isn’t blocking Calendly (use ngrok or a public endpoint to test).
Steps
// Verify HMAC-SHA256 signature (adjust header name per Calendly docs)
const crypto = require('crypto');
const body = rawBody; // // use raw request body bytes
const secret = process.env.WEBHOOK_SECRET;
const sig = req.headers['x-webhook-signature'];
const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');
if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
// accepted
}
2
A mismatch between the redirect URI or missing client config causes Calendly OAuth errors; set the exact redirect URI that OpenClaw/ClawHub uses in Calendly’s developer app, register matching scopes, and store the client ID/secret as environment variables in your skill so the runtime sends identical values during the auth flow.
Do these things precisely:
// build authorize URL used by OpenClaw runtime
const url = `https://auth.calendly.com/oauth/authorize?client_id=${process.env.CALENDLY_CLIENT_ID}&redirect_uri=${encodeURIComponent(process.env.CALENDLY_REDIRECT)}&response_type=code&scope=user:read events:write&state=${state}`;
// <b>//</b> ensure CALENDLY_REDIRECT matches exactly what Calendly has registered
3
A direct fix: map the exact Calendly webhook JSON keys to the OpenClaw Meeting fields in your Event Mapping, and for custom questions extract invitee_answers into a stable field (metadata or custom_fields) or pre-transform them in a skill before creating the Meeting. If the mapping UI can't reach nested keys, run a small transformer (skill) to normalize the payload.
Point each OpenClaw Meeting field to the matching Calendly key (e.g. start_time, end_time, invitee.email); if the UI needs flat keys, normalize first.
Extract invitee_answers into meeting.metadata.custom_questions or dedicated custom_fields; parse keys server-side if names vary.
// transformCalendly.js
function transform(calendlyWebhook){
// map top-level fields
const event = calendlyWebhook.event; // example
// // build Meeting object
return {
title: event.payload.name || 'Meeting',
start_time: event.payload.start_time,
end_time: event.payload.end_time,
attendees: [{email: event.payload.invitee.email, name: event.payload.invitee.name}],
metadata: {custom_questions: event.payload.invitee_answers || []}
};
}
4
Direct answer: The usual cause is mismatches between Calendly’s stable IDs (invitee/event), webhook retry/edits, and your OpenClaw skill's participant lookup. Ensure you persist Calendly invitee_id and event_id, use an idempotency key for each webhook action, dedupe retries, and resolve conflicting edits by comparing timestamps and stored external IDs.
Inspect raw webhook payloads and skill logs, validate signatures, persist mapping (Calendly invitee_id → participant_id), build idempotency key (event_id+invitee_id+action), and on update check stored external id before create to avoid duplicates.
// webhook handler snippet
const key = `${payload.event.uri}|${payload.invitee.id}|${payload.event_type.name}`;
// check idempotency store
if (await seen(key)) return; // dedupe
await markSeen(key);
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â