DEV Community

Cover image for Cómo Usar las APIs de Calendly: Guía para Desarrolladores sobre Integración de Programación
Roobia
Roobia

Posted on • Originally published at apidog.com

Cómo Usar las APIs de Calendly: Guía para Desarrolladores sobre Integración de Programación

TL;DR

Las APIs de Calendly te permiten automatizar flujos de programación: autentícate vía OAuth 2.0, accede a tipos de eventos y reservas con api.calendly.com, y recibe notificaciones en tiempo real por webhooks. Usa Apidog para probar y validar tus manejadores de webhooks antes de conectar con calendarios reales.

Prueba Apidog hoy

Introducción

Calendly procesa millones de reuniones cada mes y es ampliamente usado para ventas, soporte, consultas y entrevistas. Su API te permite integrar la programación automática en tus propias aplicaciones.

El flujo típico: quieres que una reserva de Calendly dispare acciones en tu sistema. Ejemplos: un usuario agenda una demo y tu CRM se actualiza; se cancela una reunión y lo notificas en tu equipo; un lead reserva y envías un cuestionario.

Esto se gestiona con webhooks: cuando ocurre un evento (reserva creada, cancelada o reprogramada), Calendly hace POST a tus endpoints. Tú procesas el payload y ejecutas la automatización.

💡 Si estás construyendo integraciones de programación, Apidog te ayuda a probar manejadores de webhooks y validar cargas útiles. Puedes simular las respuestas de Calendly durante el desarrollo y asegurarte de que tu integración maneje todos los tipos de eventos antes de conectarte a calendarios reales.

Autenticación con OAuth 2.0

Calendly utiliza OAuth 2.0 para acceder a la API. No se aceptan claves de API simples.

Crear una aplicación OAuth

  1. Ve a Calendly → Integraciones → API y Webhooks.
  2. Haz clic en "Crear Nueva Aplicación".
  3. Configura tu URI de redirección (ejemplo: https://tuapp.com/auth/calendly/callback).
  4. Obtén tu ID de cliente y secreto de cliente.

Flujo de OAuth

1. Redirige al usuario para autorizar:

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

2. El usuario autoriza y es redirigido a tu callback:

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

3. Intercambia el código por un token de acceso:

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

4. Usa el token para llamar la API:

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

Tokens de actualización

El token de acceso expira en 2 horas. Refresca así:

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

Obteniendo información del usuario

Obtener usuario actual

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

Respuesta:

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

Obtener membresía de la organización

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

Tipos de eventos

Los tipos de eventos son plantillas de reuniones (llamada de 30 min, consulta de 60 min, etc.).

Listar tipos de eventos

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

Respuesta:

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

Obtener un tipo de evento específico

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

Eventos programados (reservas)

Los eventos son las reservas efectivas realizadas en Calendly.

Listar eventos programados

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

Filtra por rango de fechas:

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

Respuesta:

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

Obtener detalles del evento

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

Obtener invitados para un evento

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

Respuesta:

{
  "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 para actualizaciones en tiempo real

Los webhooks notifican tu app sobre eventos de reservas al instante.

Crear una suscripción de webhook

curl -X POST "https://api.calendly.com/webhook_subscriptions" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tuapp.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

Eventos disponibles:

  • invitee.created - Nueva reserva realizada
  • invitee.canceled - Reserva cancelada
  • invitee.rescheduled - Reserva reprogramada

Listar suscripciones de 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

Eliminar un webhook

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

Manejo de cargas útiles de webhook

Verificar firmas de webhook

Calendly firma los webhooks en el 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')
  }

  // Procesar webhook
  handleWebhook(req.body)
  res.status(200).send('OK')
})
Enter fullscreen mode Exit fullscreen mode

Procesar eventos de reserva

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

  switch (event) {
    case 'invitee.created':
      console.log(`Nueva reserva: ${data.event.start_time}`)
      console.log(`Invitado: ${data.email}`)
      // Añadir a CRM, enviar correo de confirmación, etc.
      syncToCRM(data)
      break

    case 'invitee.canceled':
      console.log(`Reserva cancelada: ${data.event.uri}`)
      // Actualizar CRM, notificar al equipo, etc.
      removeFromCRM(data)
      break

    case 'invitee.rescheduled':
      console.log(`Reserva reprogramada: ${data.event.start_time}`)
      // Actualizar calendario, notificar al equipo, etc.
      updateCRM(data)
      break
  }
}
Enter fullscreen mode Exit fullscreen mode

Pruebas con Apidog

La autenticación OAuth de Calendly dificulta las pruebas manuales. Apidog lo simplifica.

Apidog testing Calendly

1. Simular respuestas de OAuth

Durante desarrollo, no hagas todo el flujo OAuth cada vez. Simula así:

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

2. Probar manejadores de webhooks

Crea cargas útiles simuladas para tus tests:

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

Envía estas cargas a tu endpoint de webhook y verifica el comportamiento.

3. Variables de entorno

Guarda tus credenciales y secretos en variables de entorno:

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. Validar firmas de 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')

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

Prueba webhooks de Calendly con Apidog - gratis

Errores comunes y soluciones

401 No autorizado

Causa: Token inválido o expirado.

Solución:

  1. Verifica expiración (2 horas por token)
  2. Usa el token de actualización para renovarlo
  3. Usa el header Authorization: Bearer {token} correctamente

403 Prohibido

Causa: Alcance OAuth insuficiente.

Solución: Solicita los scopes necesarios al autorizar. Los permisos dependen de lo que el usuario autorice.

404 No encontrado

Causa: El recurso no existe o el usuario no tiene acceso.

Solución:

  1. Verifica el URI del recurso
  2. Asegura que el usuario autenticado tenga acceso
  3. Verifica que el tipo o ID de evento sea válido

422 Entidad no procesable

Causa: Error de validación en la solicitud.

Solución: Revisa el detalle en la respuesta:

{
  "title": "Error de validación",
  "message": "Parámetro inválido: la URL debe ser una URL HTTPS válida"
}
Enter fullscreen mode Exit fullscreen mode

Alternativas y comparaciones

Característica Calendly Acuity Cal.com Calendly
Nivel gratuito Limitado Limitado Auto-alojado gratis
Acceso API
Webhooks
OAuth Clave API Clave API OAuth
Programación en equipo
Código abierto No No No

Calendly tiene la documentación de API y flujo OAuth más pulido. Cal.com es la alternativa open-source con autenticación por clave de API más simple.

Casos de uso en el mundo real

Integración con CRM de ventas:

Una empresa SaaS B2B integra Calendly en su web. Cuando se agenda una demo, el webhook dispara:

  1. Crear lead en Salesforce
  2. Notificar en Slack al equipo de ventas
  3. Añadir a la secuencia de marketing automation
  4. Registrar actividad en la plataforma de éxito del cliente

Plataforma de consulta:

Un sitio de servicios legales permite reservar reuniones con abogados. La integración de API:

  1. Sincroniza reservas con el sistema interno
  2. Genera links de Zoom
  3. Envía cuestionario previo 24h antes
  4. Crea expediente al finalizar la reunión

Programación de entrevistas:

Una plataforma de reclutamiento usa Calendly para entrevistas. Los webhooks:

  1. Actualizan el ATS con los datos de la entrevista
  2. Notifican al gerente por email
  3. Envían invitaciones de calendario a los participantes
  4. Hacen seguimiento de ausencias

Conclusión

Resumen rápido:

  • Calendly usa OAuth 2.0 para autenticación API
  • Accede a tipos de eventos y reservas con la API
  • Los webhooks notifican reservas en tiempo real
  • Siempre verifica las firmas de los webhooks
  • Prueba tu integración con Apidog antes de conectar con calendarios reales

Próximos pasos:

  1. Crea una app OAuth en Calendly
  2. Implementa el flujo OAuth
  3. Configura un webhook
  4. Prueba cargas útiles simuladas en Apidog
  5. Despliega a producción

Prueba webhooks de Calendly con Apidog - gratis

Preguntas frecuentes

¿Necesito un plan de pago para usar la API?

No. La API está disponible en todos los planes, incluso el gratuito (aunque limitado). Los webhooks funcionan en todos los planes.

¿Diferencia entre webhooks de usuario y de organización?

Webhooks a nivel de usuario capturan eventos de un usuario. Los de organización capturan eventos de todo el equipo. Lo más común es usar el alcance de organización.

¿Cómo obtengo el secreto de firma del webhook?

Cuando creas el webhook via API, la respuesta incluye signing_key. Guárdalo seguro: lo necesitas para verificar firmas.

¿Se pueden crear reservas vía API?

No. Solo lectura para reservas: deben hacerse por la UI de Calendly o widgets.

¿Cómo manejo zonas horarias?

Todas las marcas de tiempo están en UTC (ISO 8601). Convierte a local en tu app. La zona horaria del usuario está en el recurso de usuario.

¿Límite de velocidad?

No hay documentación pública. Usa patrones razonables y backoff exponencial si es necesario.

¿Se pueden obtener reservas históricas?

Sí, usando min_start_time y max_start_time. No hay límite de antigüedad.

¿Cómo pruebo OAuth localmente?

Utiliza un túnel como ngrok para exponer tu servidor local, configura la URI de redirección a la URL de ngrok y completa el flujo OAuth en un navegador.

Top comments (0)