Llevar las citas a tu sistema
Suscribirse a los tres eventos de una cita y procesar cada entrega.
ClínicasSolo en los workspaces de clínicas.
La clínica agenda en Vitrina y necesita que eso aparezca en el sistema de facturación, en el de recordatorios o en el tablero del jefe de sede. Esta receta lo resuelve con una suscripción a los eventos de la agenda, sin consultar cada cinco minutos y sin exportar nada.
Los tres eventos son todo el ciclo de una cita: se reserva, se mueve, se anula.
1. Una key para la integración
La suscripción tiene dueño, y el dueño decide qué trae cada entrega: un evento llega con el recurso completo solo si su dueño puede leerlo. Para la agenda basta appointments:read:
curl -X POST https://api.vitrinadev.com/api/v1/api-keys \
-H "Authorization: Bearer $VITRINA_ROOT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "clínica — agenda hacia mi sistema",
"scopes": ["webhooks:read", "webhooks:write", "appointments:read"]
}'Fíjate en lo que no pide: nada de la ficha. Esta integración mueve horas, no historias clínicas, y la credencial lo dice (Scopes).
2. La suscripción
curl -X POST https://api.vitrinadev.com/api/v1/webhooks \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://sistema.tu-clinica.cl/vitrina",
"events": ["appointment.booked", "appointment.rescheduled", "appointment.cancelled"],
"description": "agenda → sistema de la clínica",
"include_data": true
}'La respuesta trae un secret que empieza con whsec_, una sola vez. Guárdalo antes de cerrar la terminal.
Verifica la firma de cada entrega antes de escribir nada. El verificador completo, en JavaScript y en Python, con un ejemplo resuelto, está en Empezar.
3. Se reserva
Esto llegó al receptor cuando alguien reservó una hora desde la agenda de la clínica:
{
"id": "70373954-361e-466e-85b3-355ba695d11b",
"type": "appointment.booked",
"version": 1,
"created_at": "2026-09-22T03:29:47.254Z",
"tenant_id": "23970000-0000-4000-8000-000000000001",
"resource": {
"type": "appointment",
"id": "6e3f2df4-ca99-47c4-879a-d69093051961",
"url": "https://api.vitrinadev.com/api/v1/appointments/6e3f2df4-ca99-47c4-879a-d69093051961"
},
"author": {
"kind": "api_key",
"id": "24e30945-ac7c-4da7-a3aa-984abd7811bc",
"name": "agenda de la clínica"
},
"data": {
"id": "6e3f2df4-ca99-47c4-879a-d69093051961",
"display_id": "A-4",
"status": "confirmed",
"kind": "clinic",
"starts_at": "2026-09-26T13:30:00.000Z",
"ends_at": "2026-09-26T14:00:00.000Z",
"contact_id": null,
"lead_id": null,
"owner_user_id": null,
"vehicle_id": null,
"appointment_type_id": null
}
}kind: "clinic" distingue una cita de la clínica de cualquier otra clase de visita. display_id, aquí A-4, es el identificador que la gente ve en la aplicación: úsalo cuando alguien tenga que buscar la misma cita en los dos sistemas.
starts_at y ends_at son UTC, siempre. La hora con la que una persona la ve es la de su sede: timezone, en Sucursales.
El evento no trae quién es la persona. contact_id viene cuando la cita está enlazada a un contacto del workspace, y es un id, no un nombre. El resto se lee del recurso, con tu key.
4. Se mueve
{
"id": "fee58690-29d5-419c-9f3a-334e3c5ab1f6",
"type": "appointment.rescheduled",
"version": 1,
"created_at": "2026-09-22T03:29:51.979Z",
"tenant_id": "23970000-0000-4000-8000-000000000001",
"resource": {
"type": "appointment",
"id": "6e3f2df4-ca99-47c4-879a-d69093051961",
"url": "https://api.vitrinadev.com/api/v1/appointments/6e3f2df4-ca99-47c4-879a-d69093051961"
},
"author": {
"kind": "api_key",
"id": "24e30945-ac7c-4da7-a3aa-984abd7811bc",
"name": "agenda de la clínica"
},
"data": {
"id": "6e3f2df4-ca99-47c4-879a-d69093051961",
"display_id": "A-4",
"status": "confirmed",
"kind": "clinic",
"starts_at": "2026-09-26T15:00:00.000Z",
"ends_at": "2026-09-26T15:30:00.000Z",
"contact_id": null,
"lead_id": null,
"owner_user_id": null,
"vehicle_id": null,
"appointment_type_id": null
}
}Mismo id de cita, horas nuevas. Es un movimiento de la misma cita: si tu sistema crea una fila por cada appointment.booked y otra por cada appointment.rescheduled, la agenda se te duplica. Enlaza por data.id.
5. Se anula
{
"id": "f332a4e8-6d8a-424d-a904-24a82d2cbbe8",
"type": "appointment.cancelled",
"version": 1,
"created_at": "2026-09-22T03:29:56.330Z",
"tenant_id": "23970000-0000-4000-8000-000000000001",
"resource": {
"type": "appointment",
"id": "6e3f2df4-ca99-47c4-879a-d69093051961",
"url": "https://api.vitrinadev.com/api/v1/appointments/6e3f2df4-ca99-47c4-879a-d69093051961"
},
"author": {
"kind": "api_key",
"id": "24e30945-ac7c-4da7-a3aa-984abd7811bc",
"name": "agenda de la clínica"
},
"data": {
"id": "6e3f2df4-ca99-47c4-879a-d69093051961",
"display_id": "A-4",
"status": "cancelled",
"kind": "clinic",
"starts_at": "2026-09-26T15:00:00.000Z",
"ends_at": "2026-09-26T15:30:00.000Z",
"contact_id": null,
"lead_id": null,
"owner_user_id": null,
"vehicle_id": null,
"appointment_type_id": null
}
}status: "cancelled", y starts_at sigue siendo la hora que se liberó, que es el dato que tu sistema necesita para volver a ofrecerla.
Trampa
Una cita anulada no desaparece: cambia de estado
La fila sigue existiendo y el id sigue siendo el mismo. Si tu sistema borra al
recibir appointment.cancelled, pierdes el historial que después alguien va a
pedir: cuántas horas se anularon este mes, cuáles se anularon el mismo día.
Marca el estado en vez de eliminar.
6. Trata cada entrega como repetible
Reintentamos hasta cinco veces con espera creciente, y un reenvío manual vuelve a entregar el mismo evento. Tu endpoint va a verlo más de una vez: id es único por evento, guárdalo y descarta el repetido. Responde 200 rápido y procesa después; si tardas, cuenta como falla y vuelve a llegar.
Cuando algo no llegue, el registro de entregas es el primer lugar donde mirar:
curl https://api.vitrinadev.com/api/v1/webhooks/625c97b6-1856-41e8-828b-081965b2c067/deliveries \
-H "Authorization: Bearer $VITRINA_KEY"Cada intento deja una fila, con el código que respondió tu endpoint y el sobre exacto que enviamos.
La otra mitad de esta historia es preguntarle a la agenda en vez de copiarla, y está en Conectar Claude a tu agenda.