Ir al contenido

Webhooks

Cuando se dispara un evento, se envía un POST a la URL registrada con el siguiente formato.

Headers:

HeaderDescripción
Content-Typeapplication/json
X-CitaPro-EventNombre del evento (ej. booking.updated)
X-CitaPro-SignatureHMAC-SHA256 del body usando el secret del webhook
X-CitaPro-Delivery-IdUUID único de esta entrega
X-CitaPro-TimestampTimestamp Unix de la entrega

Estructura del body:

{
"event": "booking.updated",
"aggregate_id": "550e8400-e29b-41d4-a716-446655440000",
"occurred_on": "2026-03-05 14:30:00",
"data": {
"startTime": "2026-04-01 14:00:00",
"endTime": "2026-04-01 15:00:00",
"status": "confirmed",
"internalNote": null,
"clientNote": null,
"payment_status": null,
"isFromWeb": false,
"isFromAgent": false,
"amount": 75.0,
"clientId": "550e8400-e29b-41d4-a716-446655440001",
"locationId": "550e8400-e29b-41d4-a716-446655440002",
"serviceId": "550e8400-e29b-41d4-a716-446655440003",
"personId": "550e8400-e29b-41d4-a716-446655440004",
"paymentId": null,
"businessAccountId": "550e8400-e29b-41d4-a716-446655440005",
"notifyClient": true,
"lastUpdatedBy": null,
"repeatGroupId": null,
"changes": {
"status": { "old": "pending", "new": "confirmed" },
"amount": { "old": 50.0, "new": 75.0 }
}
}
}

Campo changes (solo en eventos booking.updated): El campo data.changes contiene los campos modificados. Cada clave es el nombre del campo y el valor es un objeto con old (valor anterior) y new (valor nuevo).

Campo rastreableTipo
startTimestring
endTimestring
statusstring
internalNotestring | null
clientNotestring | null
paymentStatusstring | null
amountnumber
clientIdstring
locationIdstring
serviceIdstring
personIdstring | null
paymentIdstring | null

Si changes es un objeto vacío {}, no hubo cambios detectados en los campos rastreados.

Verificación de firma: Calcula el HMAC-SHA256 del body raw con tu secret y compáralo con el header X-CitaPro-Signature:

$signature = hash_hmac('sha256', $rawBody, $webhookSecret);
$isValid = hash_equals($signature, $request->header('X-CitaPro-Signature'));

Reintentos: Si tu endpoint no responde con 2xx, se reintenta hasta 3 veces con backoff progresivo (10s, 60s, 5min).


GET /v1/webhooks

Retorna todos los endpoints de webhook configurados para el negocio.

Respuesta exitosa: 200 OK

[
{
"id": "550e8400-e29b-41d4-a716-446655440020",
"url": "https://example.com/webhook",
"events": ["booking.created", "booking.updated"],
"isActive": true,
"createdAt": "2026-03-01 10:00:00"
}
]
POST /v1/webhooks
CampoTipoRequeridoDescripción
urlstringSiURL HTTPS del endpoint
eventsstring[]SiEventos a suscribir (mínimo 1)

Eventos disponibles:

EventoDescripción
booking.createdSe creó una reserva
booking.updatedSe actualizó una reserva
booking.deletedSe eliminó una reserva
booking.status.updatedSe cambió el estado de una reserva
client.createdSe creó un cliente
client.deletedSe eliminó un cliente
time_block.createdSe creó un bloqueo de tiempo
time_block.deletedSe eliminó un bloqueo de tiempo

Ejemplo:

{
"url": "https://example.com/webhook",
"events": ["booking.created", "booking.updated", "client.created"]
}

Respuesta exitosa: 201 Created

{
"id": "550e8400-e29b-41d4-a716-446655440020",
"url": "https://example.com/webhook",
"events": ["booking.created", "booking.updated", "client.created"],
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0...",
"isActive": true
}
PUT /v1/webhooks/{id}
ParámetroTipoDescripción
idstring (UUID)ID del webhook

Body (JSON):

CampoTipoRequeridoDescripción
urlstringSiURL HTTPS del endpoint
eventsstring[]SiEventos a suscribir
isActivebooleanSiSi el webhook está activo

Ejemplo:

{
"url": "https://example.com/webhook-v2",
"events": ["booking.created"],
"isActive": false
}

Respuesta exitosa: 204 No Content

DELETE /v1/webhooks/{id}

Elimina el webhook y todo su historial de entregas.

Respuesta exitosa: 204 No Content