DEV Community

Cover image for How to Use Calendly APIs: A Developer's Guide to Scheduling Integration
Preecha
Preecha

Posted on

How to Use Calendly APIs: A Developer's Guide to Scheduling Integration

TL;DR

Calendly APIs let you automate scheduling workflows. Authenticate with OAuth 2.0, access event types and bookings through api.calendly.com, and receive real-time updates with webhooks. Use Apidog to validate webhook payloads and test your integration without creating real bookings.

Try Apidog today

Introduction

Calendly processes millions of meetings monthly for sales calls, support sessions, consultations, and interviews. Its API lets you bring that scheduling workflow into your own application.

A typical integration looks like this:

  1. A user books a demo.
  2. Calendly sends a webhook to your application.
  3. Your webhook handler creates or updates a CRM record.
  4. Your application sends notifications, questionnaires, or follow-up tasks.

Calendly webhooks support booking-created, canceled, and rescheduled events. Calendly POSTs each event to your endpoint, where you validate the request and trigger your business logic.

💡 If you’re building scheduling integrations, Apidog can help you test webhook handlers, validate payloads, and mock Calendly responses before connecting to real calendars.

Authentication with OAuth 2.0

Calendly uses OAuth 2.0 for API access. You cannot authenticate with a standalone API key.

Create an OAuth application

  1. In Calendly, go to Integrations → API & Webhooks.
  2. Select Create New Application.
  3. Configure a redirect URI, such as:
https://yourapp.com/auth/calendly/callback
Enter fullscreen mode Exit fullscreen mode
  1. Save your client ID and client secret securely.

Implement the OAuth flow

Step 1: Redirect the user to Calendly authorization

Redirect your user to Calendly’s authorization endpoint:

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

Step 2: Receive the authorization code

After the user authorizes your application, Calendly redirects to your callback URL:

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

Extract the code query parameter on your server.

Step 3: Exchange the code for tokens

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

Store the access token and refresh token securely. Do not expose either token to browser clients.

Step 4: Call the Calendly API

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

Refresh expired access tokens

Access tokens expire after two hours. Refresh them before making API calls with an expired 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: 'refresh_token',
    refresh_token: storedRefreshToken
  })
})

const refreshedTokens = await response.json()
Enter fullscreen mode Exit fullscreen mode

Update your stored tokens with the refresh response.

Getting user information

Get the current user

Use /users/me after authentication to identify the connected Calendly user:

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

Example response:

{
  "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

Save the user uri. You will use it when listing event types and scheduled events.

Get organization membership

Retrieve the authenticated user’s organization membership:

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

Use the organization URI when creating organization-scoped webhook subscriptions.

Event types

Event types are scheduling templates, such as a 30-minute call or a 60-minute consultation.

List event types

Pass the user URI as a query parameter:

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

Example response:

{
  "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

In practice, store the event type URI or scheduling URL if your application needs to associate bookings with a specific meeting template.

Get a specific event type

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

Scheduled events (bookings)

Scheduled events are the actual bookings made through Calendly.

List scheduled events

List events for a user:

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

Filter events by a UTC date range:

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

Example response:

{
  "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

Get event details

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

Get invitees for an event

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

Example response:

{
  "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

Use the invitee response to sync attendee details, cancellation status, and timezone data into your application.

Webhooks for real-time updates

Webhooks let your app react to booking changes without polling the scheduled events endpoint.

Create a webhook subscription

Create an organization-scoped subscription:

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

Available events:

  • invitee.created — a new booking was made
  • invitee.canceled — a booking was canceled
  • invitee.rescheduled — a booking was rescheduled

Your webhook URL must be reachable over HTTPS.

List webhook subscriptions

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

Delete a webhook subscription

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

Handling webhook payloads

A production webhook endpoint should:

  1. Read the raw request body.
  2. Verify the Calendly-Webhook-Signature header.
  3. Return a successful response after accepting the event.
  4. Route the event to idempotent business logic.

Verify webhook signatures

Calendly signs webhooks using the Calendly-Webhook-Signature header.

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)
  )
}
Enter fullscreen mode Exit fullscreen mode

Use the raw request body for signature validation. Serializing a parsed body with JSON.stringify() can produce a different byte sequence than the original request.

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')
  }

  handleWebhook(req.body)

  return res.status(200).send('OK')
})
Enter fullscreen mode Exit fullscreen mode

Process booking events

Route behavior based on the webhook event field:

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

  switch (event) {
    case 'invitee.created':
      console.log(`New booking: ${data.event.start_time}`)
      console.log(`Invitee: ${data.email}`)
      // Add to CRM, send confirmation email, etc.
      syncToCRM(data)
      break

    case 'invitee.canceled':
      console.log(`Booking canceled: ${data.event.uri}`)
      // Update CRM, notify team, etc.
      removeFromCRM(data)
      break

    case 'invitee.rescheduled':
      console.log(`Booking rescheduled: ${data.event.start_time}`)
      // Update calendar, notify team, etc.
      updateCRM(data)
      break
  }
}
Enter fullscreen mode Exit fullscreen mode

Keep these handlers idempotent so repeated webhook deliveries do not create duplicate CRM records or notifications.

Testing with Apidog

Calendly’s OAuth requirement can complicate local development and automated testing. Use Apidog to mock token responses and send webhook payloads to your handler.

Image

1. Mock OAuth responses

Avoid completing the full OAuth flow for every development test. Mock a token response:

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

Use this response to test token storage, authenticated API requests, and refresh-token logic.

2. Test webhook handlers

Create a mock webhook payload:

{
  "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

Send it to your local or deployed webhook endpoint, then verify that your application creates the expected CRM record, notification, or follow-up workflow.

Test each supported event:

  • invitee.created
  • invitee.canceled
  • invitee.rescheduled

3. Configure environment variables

Keep credentials and secrets out of source control:

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. Validate webhook signatures

Use a test script to confirm the signature header is present and valid:

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')

  // Verify signature
  const valid = verifySignature(payload, signature, secret)
  pm.expect(valid).to.be.true
})
Enter fullscreen mode Exit fullscreen mode

Test Calendly webhooks with Apidog — free.

Common errors and fixes

401 Unauthorized

Cause: The access token is invalid or expired.

Fix:

  • Confirm that the token has not passed its two-hour expiry.
  • Refresh the token before retrying the request.
  • Use the Authorization: Bearer {token} header format.

403 Forbidden

Cause: The OAuth authorization is insufficient for the requested resource.

Fix: Request the required authorization during the OAuth flow. Calendly scopes are implicit based on what the user authorizes.

404 Not Found

Cause: The resource does not exist or the authenticated user cannot access it.

Fix:

  • Verify the resource URI.
  • Confirm that the authenticated user has access.
  • Check that the event type ID or event ID is valid.

422 Unprocessable Entity

Cause: The request failed validation.

Check the response body for the invalid parameter:

{
  "title": "Validation Error",
  "message": "Invalid parameter: url must be a valid HTTPS URL"
}
Enter fullscreen mode Exit fullscreen mode

For webhook subscriptions, confirm that the callback URL is a valid HTTPS endpoint.

Alternatives and comparisons

Feature Calendly Acuity Cal.com Calendly
Free tier Limited Limited Self-hosted free ✓
API access ✓ ✓ ✓ ✓
Webhooks ✓ ✓ ✓ ✓
OAuth ✓ API key API key OAuth
Team scheduling ✓ ✓ ✓ ✓
Open source No No Yes No

Calendly has a polished API documentation and OAuth flow. Cal.com is an open-source alternative with simpler API key authentication.

Real-world use cases

Sales CRM integration

A B2B SaaS company embeds Calendly on its pricing page. When someone books a demo, a webhook can:

  • Create a lead in Salesforce.
  • Send a Slack notification to the sales team.
  • Add the lead to a marketing automation sequence.
  • Log an activity in a customer success platform.

Consultation platform

A legal services platform lets clients book consultations with lawyers. The integration can:

  • Sync bookings to an internal scheduling system.
  • Generate Zoom meeting links.
  • Send an intake questionnaire 24 hours before the meeting.
  • Create a case file when the meeting completes.

Interview scheduling

A recruiting platform uses Calendly for candidate interviews. Webhooks can:

  • Update the ATS with interview details.
  • Notify the hiring manager by email.
  • Send calendar invitations to participants.
  • Track no-shows for follow-up.

Developers building multi-platform integrations often need marketplace access in addition to scheduling—integrating the Amazon SP API follows a comparable OAuth and request-signing pattern that is worth understanding alongside Calendly.

Conclusion

You now have the implementation path for a Calendly integration:

  • Authenticate users with OAuth 2.0.
  • Retrieve user, organization, event type, and scheduled event data.
  • Create webhook subscriptions for real-time booking updates.
  • Verify webhook signatures before processing payloads.
  • Test OAuth and webhook flows with Apidog before connecting real calendars.

Next steps:

  1. Create a Calendly OAuth application.
  2. Implement the authorization callback and token exchange.
  3. Store and refresh tokens securely.
  4. Create a webhook subscription.
  5. Test created, canceled, and rescheduled payloads in Apidog.
  6. Deploy your HTTPS webhook endpoint.

Test Calendly webhooks with Apidog — free.

FAQ

Do I need a paid Calendly plan to use the API?

No. The API is available on all plans, including free plans. Free plans have limited features, but webhooks are available on all plans.

What is the difference between user-level and organization-level webhooks?

User-level webhooks capture events for one user. Organization-level webhooks capture events for all team members. Most integrations use organization scope.

How do I get the webhook signing secret?

When you create a webhook through the API, the response includes a signing_key. Store it securely and use it to verify webhook signatures.

Can I create bookings through the API?

No. Calendly does not provide an API endpoint to create bookings. Bookings must happen through Calendly’s UI or embedded widgets. The API is read-only for bookings.

How do I handle timezone conversions?

API timestamps are UTC in ISO 8601 format. Convert them to local time in your application. The user resource includes the user’s timezone.

What is the rate limit?

Calendly does not publicly document rate limits. Use reasonable request patterns. If you encounter limits, implement exponential backoff.

Can I retrieve historical bookings?

Yes. Query scheduled events with min_start_time and max_start_time. There is no limit on how far back you can query.

How do I test the OAuth flow locally?

Use a tunneling service such as ngrok to expose your local server. Set the OAuth redirect URI to your ngrok URL, complete the authorization flow in a browser, and inspect the callback request.

Top comments (0)