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.
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
- Ve a Calendly → Integraciones → API y Webhooks.
- Haz clic en "Crear Nueva Aplicación".
- Configura tu URI de redirección (ejemplo:
https://tuapp.com/auth/calendly/callback). - 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
2. El usuario autoriza y es redirigido a tu callback:
https://yourapp.com/auth/calendly/callback?code=AUTHORIZATION_CODE
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()
4. Usa el token para llamar la API:
curl -X GET "https://api.calendly.com/users/me" \
-H "Authorization: Bearer ACCESS_TOKEN"
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
})
})
Obteniendo información del usuario
Obtener usuario actual
curl -X GET "https://api.calendly.com/users/me" \
-H "Authorization: Bearer ACCESS_TOKEN"
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"
}
}
Obtener membresía de la organización
curl -X GET "https://api.calendly.com/organization_memberships/me" \
-H "Authorization: Bearer ACCESS_TOKEN"
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"
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
}
}
Obtener un tipo de evento específico
curl -X GET "https://api.calendly.com/event_types/ETC123" \
-H "Authorization: Bearer ACCESS_TOKEN"
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"
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"
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"
}
}
Obtener detalles del evento
curl -X GET "https://api.calendly.com/scheduled_events/EVENT_ID" \
-H "Authorization: Bearer ACCESS_TOKEN"
Obtener invitados para un evento
curl -X GET "https://api.calendly.com/scheduled_events/EVENT_ID/invitees" \
-H "Authorization: Bearer ACCESS_TOKEN"
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
}
]
}
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"
}'
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"
Eliminar un webhook
curl -X DELETE "https://api.calendly.com/webhook_subscriptions/WEBHOOK_ID" \
-H "Authorization: Bearer ACCESS_TOKEN"
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')
})
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
}
}
Pruebas con Apidog
La autenticación OAuth de Calendly dificulta las pruebas manuales. Apidog lo simplifica.
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
}
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"
}
}
}
}
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
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
})
Prueba webhooks de Calendly con Apidog - gratis
Errores comunes y soluciones
401 No autorizado
Causa: Token inválido o expirado.
Solución:
- Verifica expiración (2 horas por token)
- Usa el token de actualización para renovarlo
- 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:
- Verifica el URI del recurso
- Asegura que el usuario autenticado tenga acceso
- 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"
}
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 | Sí | 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:
- Crear lead en Salesforce
- Notificar en Slack al equipo de ventas
- Añadir a la secuencia de marketing automation
- 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:
- Sincroniza reservas con el sistema interno
- Genera links de Zoom
- Envía cuestionario previo 24h antes
- Crea expediente al finalizar la reunión
Programación de entrevistas:
Una plataforma de reclutamiento usa Calendly para entrevistas. Los webhooks:
- Actualizan el ATS con los datos de la entrevista
- Notifican al gerente por email
- Envían invitaciones de calendario a los participantes
- 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:
- Crea una app OAuth en Calendly
- Implementa el flujo OAuth
- Configura un webhook
- Prueba cargas útiles simuladas en Apidog
- 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)