Calendly 连接器
通过 Tajo 将 Calendly 连接到 Brevo,自动将会议受邀者同步为联系人,根据预约事件触发邮件序列,简化销售和引导工作流。
概览
| 属性 | 值 |
|---|---|
| 平台 | Calendly |
| 类别 | 日程(自定义) |
| 设置复杂度 | 简单 |
| 官方集成 | 否 |
| 同步数据 | 事件、联系人、预约、取消 |
| 认证方式 | OAuth 2.0 / 个人访问令牌 |
功能
- 受邀者同步 - 自动将会议受邀者创建为 Brevo 联系人
- 预约触发 - 会议预约时触发 Brevo 自动化
- 取消处理 - 取消时触发重参与流程
- 未出席检测 - 受邀者缺席时更新联系人状态
- 事件类型映射 - 将不同 Calendly 事件类型映射到 Brevo 列表
- 日程 API - 直接在应用中构建日程功能,无需跳转
前提条件
开始之前,请确保您已具备:
- Calendly 账户(API 访问需要专业版或以上)
- 来自 Calendly 集成 的个人访问令牌
- 具有 API 访问权限的 Brevo 账户
- 具有连接器权限的 Tajo 账户
认证
个人访问令牌
# Generate at https://calendly.com/integrations/api_webhooksexport CALENDLY_ACCESS_TOKEN=your_personal_access_tokenexport TAJO_API_KEY=your_tajo_api_keyexport BREVO_API_KEY=your_brevo_api_keyOAuth 2.0
// OAuth 2.0 Authorization Code Flowconst authUrl = 'https://auth.calendly.com/oauth/authorize?' + new URLSearchParams({ client_id: process.env.CALENDLY_CLIENT_ID, redirect_uri: 'https://your-app.com/callback', response_type: 'code' });
// Exchange code for tokenconst tokenResponse = await fetch('https://auth.calendly.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authorizationCode, client_id: process.env.CALENDLY_CLIENT_ID, client_secret: process.env.CALENDLY_CLIENT_SECRET, redirect_uri: 'https://your-app.com/callback' })});配置
基础设置
connectors: calendly: enabled: true access_token: "${CALENDLY_ACCESS_TOKEN}"
sync: contacts: true events: true cancellations: true
event_mapping: discovery_call: list_id: 10 event_type_uri: "https://api.calendly.com/event_types/abc123" demo: list_id: 11 event_type_uri: "https://api.calendly.com/event_types/xyz789"
webhook: signing_key: "${CALENDLY_WEBHOOK_SIGNING_KEY}"字段映射
field_mapping: email: email name: FIRSTNAME questions_and_answers: company: COMPANY role: JOB_TITLE phone: SMS event_type_name: CALENDLY_EVENT_TYPE scheduled_at: MEETING_DATE status: BOOKING_STATUSAPI 端点
| 端点 | 方法 | 描述 |
|---|---|---|
https://api.calendly.com/users/me | GET | 获取当前用户 |
https://api.calendly.com/event_types | GET | 列出事件类型 |
https://api.calendly.com/scheduled_events | GET | 列出已排期事件 |
https://api.calendly.com/scheduled_events/{uuid} | GET | 获取已排期事件 |
https://api.calendly.com/scheduled_events/{uuid}/invitees | GET | 列出受邀者 |
https://api.calendly.com/scheduling_links | POST | 创建日程链接 |
https://api.calendly.com/webhook_subscriptions | POST | 创建 Webhook |
https://api.calendly.com/webhook_subscriptions | GET | 列出 Webhook |
https://api.calendly.com/invitee_no_shows/{uuid} | GET | 获取未出席状态 |
代码示例
初始化连接器
import { TajoClient } from '@tajo/sdk';
const tajo = new TajoClient({ apiKey: process.env.TAJO_API_KEY, brevoApiKey: process.env.BREVO_API_KEY});
await tajo.connectors.connect('calendly', { accessToken: process.env.CALENDLY_ACCESS_TOKEN});列出已排期事件
// Retrieve scheduled eventsconst response = await fetch( 'https://api.calendly.com/scheduled_events?' + new URLSearchParams({ user: 'https://api.calendly.com/users/YOUR_USER_ID', min_start_time: '2024-01-01T00:00:00Z', max_start_time: '2024-12-31T23:59:59Z', status: 'active', count: 100 }), { headers: { 'Authorization': `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}`, 'Content-Type': 'application/json' } });
const events = await response.json();将受邀者同步到 Brevo
// Get invitees for a scheduled event and sync to Brevoconst inviteesResponse = await fetch( `https://api.calendly.com/scheduled_events/${eventUuid}/invitees`, { headers: { 'Authorization': `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}` } });
const { collection } = await inviteesResponse.json();
for (const invitee of collection) { await tajo.contacts.sync({ email: invitee.email, attributes: { FIRSTNAME: invitee.name, CALENDLY_EVENT_TYPE: invitee.event, MEETING_DATE: invitee.created_at, BOOKING_STATUS: invitee.status }, listIds: [10] });}设置 Webhook 订阅
// Subscribe to Calendly eventsconst webhook = await fetch( 'https://api.calendly.com/webhook_subscriptions', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://api.tajo.io/webhooks/calendly', events: [ 'invitee.created', 'invitee.canceled', 'invitee_no_show.created' ], organization: 'https://api.calendly.com/organizations/YOUR_ORG_ID', scope: 'organization', signing_key: process.env.CALENDLY_WEBHOOK_SIGNING_KEY }) });处理 Webhook 事件
app.post('/webhooks/calendly', async (req, res) => { // Verify webhook signature const signature = req.headers['calendly-webhook-signature']; const isValid = verifyCalendlySignature( req.rawBody, signature, process.env.CALENDLY_WEBHOOK_SIGNING_KEY );
if (!isValid) return res.status(401).send('Unauthorized');
const { event, payload } = req.body;
switch (event) { case 'invitee.created': await tajo.contacts.sync({ email: payload.email, attributes: { BOOKING_STATUS: 'booked' }, listIds: [10] }); break; case 'invitee.canceled': await tajo.contacts.update(payload.email, { attributes: { BOOKING_STATUS: 'cancelled' } }); break; case 'invitee_no_show.created': await tajo.contacts.update(payload.email, { attributes: { BOOKING_STATUS: 'no_show' } }); break; }
res.status(200).send('OK');});速率限制
| 资源 | 限制 | 说明 |
|---|---|---|
| API 请求 | 6,000/分钟 | 组织范围限制 |
| Webhook 订阅 | 每组织 30 个 | 跨所有事件类型 |
| 日程链接 | 无限制 | 无每分钟限制 |
分页
Calendly API 响应使用游标分页。使用 pagination 对象中的 next_page_token 检索更多结果。默认页面大小为 20 条,最大 100 条。
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 未收到 Webhook | 范围错误 | 对 Webhook 使用 organization 范围 |
| 401 Unauthorized | 令牌已过期 | 生成新令牌或刷新 OAuth 令牌 |
| 受邀者数据缺失 | 未配置问题 | 向事件类型添加自定义问题 |
| 重复联系人 | 无去重逻辑 | 使用邮箱作为 upsert 的唯一标识符 |
| 速率限制 429 | 请求过多 | 实施退避和请求批处理 |
调试模式
connectors: calendly: debug: true log_level: verbose log_webhooks: true最佳实践
- 使用 Webhook - 订阅
invitee.created和invitee.canceled以实现实时同步 - 添加自定义问题 - 收集公司、职位和电话数据,丰富联系人档案
- 映射事件类型 - 为每个 Calendly 事件类型分配不同的 Brevo 列表
- 处理未出席 - 跟踪未出席情况以调整线索评分和跟进序列
- 使用日程链接 - 生成唯一日程链接,提供个性化预约体验
- 设置组织范围 - 使用组织级 Webhook 捕获所有团队成员的事件
安全
- OAuth 2.0 - 基于范围的令牌认证
- Webhook 签名 - 入站 Webhook 的 HMAC 签名验证
- 仅 HTTPS - 所有 API 端点需要 TLS 加密
- 令牌过期 - OAuth 令牌过期需要刷新流程
- 最小范围 - 仅请求所需的 OAuth 范围
- 安全存储 - 在环境变量或密钥管理器中存储令牌