DEV Community

Cover image for Hướng Dẫn Lập Trình Viên Sử Dụng Calendly APIs: Tích Hợp Lịch Hẹn
Sebastian Petrus
Sebastian Petrus

Posted on • Originally published at apidog.com

Hướng Dẫn Lập Trình Viên Sử Dụng Calendly APIs: Tích Hợp Lịch Hẹn

TÓM TẮT

API Calendly cho phép bạn tự động hóa quy trình lên lịch. Xác thực bằng OAuth 2.0, truy cập các loại sự kiện, đặt lịch qua api.calendly.com và nhận cập nhật thời gian thực qua webhooks. Để kiểm thử, hãy sử dụng Apidog để xác thực payload webhook và kiểm tra tích hợp mà không cần tạo lịch thật.

Dùng thử Apidog ngay hôm nay

Giới thiệu

Calendly xử lý hàng triệu cuộc họp mỗi tháng cho bán hàng, hỗ trợ, tư vấn, phỏng vấn. API cho phép bạn nhúng khả năng lên lịch vào ứng dụng riêng.

Mô hình phổ biến: khi có đặt lịch Calendly, hệ thống của bạn xử lý tự động (ví dụ: cập nhật CRM, gửi email, thông báo nhóm). API Calendly hỗ trợ thực hiện thông qua webhook — khi có sự kiện (tạo, hủy, reschedule), Calendly sẽ POST đến endpoint của bạn để bạn xử lý.

💡Nếu bạn xây dựng tích hợp lên lịch, Apidog giúp kiểm thử trình xử lý webhook, xác thực payload. Bạn có thể mô phỏng phản hồi Calendly trong quá trình phát triển để đảm bảo tích hợp hoạt động đúng.

Xác thực với OAuth 2.0

Calendly sử dụng OAuth 2.0 cho API, không hỗ trợ khóa API thuần.

Tạo ứng dụng OAuth

  1. Vào Calendly → Tích hợp → API & Webhooks
  2. Nhấp “Tạo ứng dụng mới”
  3. Đặt URI chuyển hướng (ví dụ: https://yourapp.com/auth/calendly/callback)
  4. Lấy client ID và client secret

Luồng OAuth

Bước 1: Chuyển hướng người dùng để ủy quyền

https://auth.calendly.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID&
  response_type=code&
  redirect_uri=https://yourapp.com/auth/calendly/callback
Enter fullscreen mode Exit fullscreen mode

Bước 2: Người dùng ủy quyền và được chuyển hướng về

https://yourapp.com/auth/calendly/callback?code=AUTHORIZATION_CODE
Enter fullscreen mode Exit fullscreen mode

Bước 3: Đổi mã lấy access token

const response = await fetch('https://auth.calendly.com/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Basic ' + Buffer.from(clientId + ':' + clientSecret).toString('base64')
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    redirect_uri: 'https://yourapp.com/auth/calendly/callback'
  })
})

const { access_token, refresh_token, expires_in } = await response.json()
Enter fullscreen mode Exit fullscreen mode

Bước 4: Sử dụng token

curl -X GET "https://api.calendly.com/users/me" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Refresh token

Access token hết hạn sau 2 giờ. Dùng refresh token để lấy token mới:

const response = await fetch('https://auth.calendly.com/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Basic ' + Buffer.from(clientId + ':' + clientSecret).toString('base64')
  },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: storedRefreshToken
  })
})
Enter fullscreen mode Exit fullscreen mode

Lấy thông tin người dùng

Lấy người dùng hiện tại

curl -X GET "https://api.calendly.com/users/me" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Phản hồi:

{
  "resource": {
    "avatar_url": "https://calendly.com/avatar.jpg",
    "created_at": "2024-01-15T10:00:00Z",
    "current_organization": "https://api.calendly.com/organizations/ABC123",
    "email": "you@example.com",
    "name": "John Doe",
    "scheduling_url": "https://calendly.com/johndoe",
    "slug": "johndoe",
    "timezone": "America/New_York",
    "uri": "https://api.calendly.com/users/ABC123"
  }
}
Enter fullscreen mode Exit fullscreen mode

Lấy tư cách thành viên tổ chức

curl -X GET "https://api.calendly.com/organization_memberships/me" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Loại sự kiện

Loại sự kiện là các mẫu (template) cuộc họp mà người dùng tạo (30 phút, tư vấn 60 phút,...).

Liệt kê các loại sự kiện

curl -X GET "https://api.calendly.com/event_types?user=https://api.calendly.com/users/ABC123" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Phản hồi:

{
  "resource": {
    "uri": "https://api.calendly.com/event_types/ETC123",
    "active": true,
    "booking_method": "instant",
    "color": "#0066FF",
    "created_at": "2024-01-15T10:00:00Z",
    "description_html": "<p>30-minute consultation</p>",
    "duration": 30,
    "internal_note": "Use Zoom link",
    "kind": "solo",
    "name": "30 Min Consultation",
    "pooling_type": null,
    "profile": {
      "name": "John Doe",
      "type": "User",
      "owner": "https://api.calendly.com/users/ABC123"
    },
    "scheduling_url": "https://calendly.com/johndoe/30min",
    "slug": "30min",
    "type": "StandardEventType"
  },
  "pagination": {
    "count": 1,
    "next_page": null
  }
}
Enter fullscreen mode Exit fullscreen mode

Lấy một loại sự kiện cụ thể

curl -X GET "https://api.calendly.com/event_types/ETC123" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Sự kiện đã lên lịch (đặt lịch)

Các sự kiện là các booking thực tế được tạo qua Calendly.

Liệt kê các sự kiện đã lên lịch

curl -X GET "https://api.calendly.com/scheduled_events?user=https://api.calendly.com/users/ABC123" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Lọc theo khoảng thời gian:

curl -X GET "https://api.calendly.com/scheduled_events?min_start_time=2026-03-01T00:00:00Z&max_start_time=2026-03-31T23:59:59Z" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Phản hồi:

{
  "resource": {
    "uri": "https://api.calendly.com/scheduled_events/ABC123",
    "status": "active",
    "tracking": {
      "utm_campaign": "spring_sale",
      "utm_source": "email",
      "utm_medium": "newsletter"
    },
    "created_at": "2026-03-24T10:00:00Z",
    "end_time": "2026-03-25T11:00:00Z",
    "event_type": "https://api.calendly.com/event_types/ETC123",
    "invitees_counter": {
      "active": 1,
      "limit": 1,
      "total": 1
    },
    "location": {
      "type": "zoom",
      "join_url": "https://zoom.us/j/123456789"
    },
    "start_time": "2026-03-25T10:30:00Z",
    "updated_at": "2026-03-24T10:00:00Z"
  }
}
Enter fullscreen mode Exit fullscreen mode

Lấy chi tiết sự kiện

curl -X GET "https://api.calendly.com/scheduled_events/EVENT_ID" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Lấy người được mời cho một sự kiện

curl -X GET "https://api.calendly.com/scheduled_events/EVENT_ID/invitees" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Phản hồi:

{
  "resource": [
    {
      "cancel_url": "https://calendly.com/cancellations/ABC123",
      "created_at": "2026-03-24T10:00:00Z",
      "email": "jane@example.com",
      "event": "https://api.calendly.com/scheduled_events/ABC123",
      "name": "Jane Smith",
      "new_invitee": null,
      "old_invitee": null,
      "reschedule_url": "https://calendly.com/reschedulings/ABC123",
      "status": "active",
      "text_reminder_number": "+15551234567",
      "timezone": "America/New_York",
      "tracking": {
        "utm_campaign": null,
        "utm_source": null
      },
      "updated_at": "2026-03-24T10:00:00Z",
      "uri": "https://api.calendly.com/scheduled_event_invitees/INV123",
      "canceled": null
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Webhooks để cập nhật thời gian thực

Webhooks giúp ứng dụng nhận thông báo về các sự kiện đặt lịch.

Tạo đăng ký webhook

curl -X POST "https://api.calendly.com/webhook_subscriptions" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/calendly",
    "events": [
      "invitee.created",
      "invitee.canceled",
      "invitee.rescheduled"
    ],
    "organization": "https://api.calendly.com/organizations/ORG123",
    "scope": "organization"
  }'
Enter fullscreen mode Exit fullscreen mode

Các sự kiện có sẵn:

  • invitee.created - Đặt lịch mới được tạo
  • invitee.canceled - Đặt lịch bị hủy
  • invitee.rescheduled - Đặt lịch được lên lịch lại

Liệt kê các đăng ký webhook

curl -X GET "https://api.calendly.com/webhook_subscriptions?organization=https://api.calendly.com/organizations/ORG123" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Xóa một webhook

curl -X DELETE "https://api.calendly.com/webhook_subscriptions/WEBHOOK_ID" \
  -H "Authorization: Bearer ACCESS_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Xử lý payload webhook

Xác minh chữ ký webhook

Calendly ký webhook bằng header Calendly-Webhook-Signature:

import crypto from 'crypto'

function verifySignature(payload, signature, secret) {
  const [t, v1] = signature.split(',')
  const timestamp = t.split('=')[1]
  const hash = v1.split('=')[1]

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + payload)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(hash),
    Buffer.from(expectedSignature)
  )
}

app.post('/webhooks/calendly', (req, res) => {
  const signature = req.headers['calendly-webhook-signature']
  const payload = JSON.stringify(req.body)

  if (!verifySignature(payload, signature, process.env.CALENDLY_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }

  // Xử lý webhook
  handleWebhook(req.body)
  res.status(200).send('OK')
})
Enter fullscreen mode Exit fullscreen mode

Xử lý các sự kiện đặt lịch

function handleWebhook(payload) {
  const { event, payload: data } = payload

  switch (event) {
    case 'invitee.created':
      console.log(`Đặt lịch mới: ${data.event.start_time}`)
      console.log(`Người được mời: ${data.email}`)
      // Thêm vào CRM, gửi email xác nhận...
      syncToCRM(data)
      break

    case 'invitee.canceled':
      console.log(`Đặt lịch bị hủy: ${data.event.uri}`)
      // Cập nhật CRM, thông báo nhóm...
      removeFromCRM(data)
      break

    case 'invitee.rescheduled':
      console.log(`Đặt lịch được lên lịch lại: ${data.event.start_time}`)
      // Cập nhật lịch, thông báo nhóm...
      updateCRM(data)
      break
  }
}
Enter fullscreen mode Exit fullscreen mode

Kiểm thử với Apidog

API Calendly yêu cầu OAuth, khiến việc kiểm thử thủ công phức tạp. Apidog giúp đơn giản hóa quy trình này.

1. Mô phỏng phản hồi OAuth

Khi phát triển, bạn có thể mô phỏng phản hồi token thay vì chạy toàn bộ luồng OAuth:

{
  "access_token": "mock_access_token",
  "refresh_token": "mock_refresh_token",
  "expires_in": 7200,
  "created_at": 1700000000
}
Enter fullscreen mode Exit fullscreen mode

2. Kiểm thử trình xử lý webhook

Tạo payload webhook giả lập:

{
  "created_at": "2026-03-24T10:00:00Z",
  "event": "invitee.created",
  "payload": {
    "email": "test@example.com",
    "name": "Test User",
    "event": {
      "start_time": "2026-03-25T10:30:00Z",
      "end_time": "2026-03-25T11:00:00Z",
      "event_type": {
        "name": "30 Min Consultation"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Gửi đến endpoint webhook và xác minh logic xử lý.

3. Biến môi trường

Thiết lập biến môi trường cho quy trình OAuth & webhook:

CALENDLY_CLIENT_ID: abc123
CALENDLY_CLIENT_SECRET: xyz789
CALENDLY_ACCESS_TOKEN: stored_token
CALENDLY_REFRESH_TOKEN: stored_refresh
CALENDLY_WEBHOOK_SECRET: webhook_signing_secret
Enter fullscreen mode Exit fullscreen mode

4. Xác thực chữ ký webhook

pm.test('Webhook signature is valid', () => {
  const signature = pm.request.headers.get('Calendly-Webhook-Signature')
  pm.expect(signature).to.exist

  const payload = pm.request.body.raw
  const secret = pm.environment.get('CALENDLY_WEBHOOK_SECRET')

  // Xác minh chữ ký
  const valid = verifySignature(payload, signature, secret)
  pm.expect(valid).to.be.true
})
Enter fullscreen mode Exit fullscreen mode

Kiểm thử webhook Calendly với Apidog hoàn toàn miễn phí.

Các lỗi phổ biến và cách khắc phục

401 Không được ủy quyền

Nguyên nhân: Token không hợp lệ hoặc hết hạn.

Khắc phục:

  1. Kiểm tra token còn hạn (token hết hạn sau 2 giờ)
  2. Sử dụng refresh token lấy access token mới
  3. Đảm bảo header Authorization là Bearer {token}

403 Bị cấm

Nguyên nhân: Không đủ phạm vi OAuth.

Khắc phục:

  1. Đảm bảo access token có đủ scope
  2. Khi yêu cầu ủy quyền, bao gồm scope cần thiết
  3. Scope của Calendly dựa vào những gì user cấp

404 Không tìm thấy

Nguyên nhân: Tài nguyên không tồn tại hoặc không có quyền truy cập.

Khắc phục:

  1. Xác minh URI tài nguyên chính xác
  2. Đảm bảo user xác thực có quyền truy cập tài nguyên
  3. Kiểm tra ID sự kiện/loại sự kiện hợp lệ

422 Thực thể không thể xử lý

Nguyên nhân: Lỗi xác thực trong request.

Khắc phục: Xem chi tiết trong phản hồi:

{
  "title": "Lỗi xác thực",
  "message": "Tham số không hợp lệ: url phải là một URL HTTPS hợp lệ"
}
Enter fullscreen mode Exit fullscreen mode

Các lựa chọn thay thế và so sánh

Tính năng Calendly Acuity Cal.com Calendly
Gói miễn phí Hạn chế Hạn chế Miễn phí tự lưu trữ
Truy cập API
Webhooks
OAuth Khóa API Khóa API OAuth
Lên lịch nhóm
Mã nguồn mở Không Không Không

Calendly có API docs và luồng OAuth hoàn thiện nhất. Cal.com là lựa chọn mã nguồn mở, xác thực bằng API key đơn giản hơn.

Các trường hợp sử dụng thực tế

Tích hợp CRM bán hàng:

Một công ty SaaS B2B nhúng Calendly vào trang giá. Khi có booking demo, webhook kích hoạt:

  1. Tạo lead trong Salesforce
  2. Gửi Slack thông báo cho sales team
  3. Thêm vào automation marketing
  4. Ghi lại hoạt động trong hệ thống chăm sóc khách hàng

Nền tảng tư vấn:

Khách hàng đặt lịch tư vấn với luật sư. API tích hợp:

  1. Đồng bộ lịch với hệ thống nội bộ
  2. Tạo link Zoom
  3. Gửi survey 24 giờ trước
  4. Tạo hồ sơ vụ việc sau khi họp

Lên lịch phỏng vấn:

Nền tảng tuyển dụng tích hợp webhook Calendly:

  1. Cập nhật ATS với chi tiết phỏng vấn
  2. Email thông báo cho manager
  3. Gửi calendar invite cho tất cả
  4. Theo dõi các trường hợp vắng mặt

Kết luận

Bạn đã biết cách:

  • Xác thực API Calendly bằng OAuth 2.0
  • Truy cập loại sự kiện, sự kiện đã đặt lịch qua API
  • Dùng webhook nhận thông báo thời gian thực
  • Xác minh chữ ký webhook đảm bảo bảo mật
  • Kiểm thử toàn bộ quy trình với Apidog trước khi kết nối lịch thật

Các bước tiếp theo:

  1. Tạo ứng dụng OAuth trong Calendly
  2. Triển khai luồng OAuth
  3. Thiết lập đăng ký webhook
  4. Kiểm thử với payload giả lập trong Apidog
  5. Đưa vào môi trường production

Kiểm thử webhook Calendly với Apidog - miễn phí.

Câu hỏi thường gặp

Tôi có cần gói trả phí của Calendly để dùng API không?

Không. API có trên mọi gói kể cả miễn phí, nhưng tính năng sẽ hạn chế. Webhook cũng có ở mọi gói.

Webhook cấp user và cấp tổ chức khác gì nhau?

Webhook user chỉ ghi nhận sự kiện của một user. Webhook tổ chức ghi nhận sự kiện cho cả nhóm. Hầu hết tích hợp nên dùng scope tổ chức.

Làm sao lấy secret ký webhook?

Khi tạo webhook qua API, response trả về signing_key. Lưu trữ an toàn để xác minh chữ ký webhook.

Có thể tạo đặt lịch qua API không?

Không. Calendly chưa hỗ trợ endpoint tạo lịch qua API, chỉ có quyền đọc các booking.

Xử lý chuyển đổi múi giờ như thế nào?

Tất cả timestamp API trả về là UTC (ISO 8601). Chuyển về local trong ứng dụng. Múi giờ user có trong resource user.

Giới hạn tốc độ là gì?

Calendly không công bố rate limit. Hãy thực hiện request hợp lý, nếu gặp limit thì dùng exponential backoff.

Có thể lấy các lịch trong quá khứ không?

Có. Dùng min_start_time, max_start_time để truy vấn lịch sử, không giới hạn thời gian.

Kiểm thử luồng OAuth cục bộ thế nào?

Dùng dịch vụ tunneling (ngrok) để expose server local. Đặt redirect URI là URL ngrok, hoàn thành OAuth trên browser và kiểm tra callback.

Top comments (0)