Webhooks: cómo funcionan
We Are Transfers hace POST a tu URL cada vez que una reserva cambia. Firmado, con reintentos, y con el cuerpo recortado a los scopes de tu integración.
El camino de un evento
text
reserva cambia ──► evento en cola (mismo commit)
│
▼ worker, cada minuto
una entrega por webhook suscrito
│
▼ POST https://tu-url (firma HMAC, 10 s de espera)
2xx → entregado · otra cosa → reintento- El evento se escribe en la misma transacción que el cambio de la reserva: no se pierde si el worker está caído.
- Cada webhook recibe su propia entrega: uno caído no frena a los demás.
- El cuerpo se construye al entregar, con los scopes de la integración en ese momento. Sin
passengers:readnunca llega un teléfono. - El orden no está garantizado entre eventos distintos. Usa
created_aty, ante la duda, vuelve a leer la reserva.
La petición que recibes
| Cabecera | Qué es |
|---|---|
X-WAT-Event | Tipo de evento (booking.completed…). |
X-WAT-Event-Id | UUID del evento. Se repite en los reintentos: deduplica por él. |
X-WAT-Delivery | UUID de la entrega. Es el mismo en todos los reintentos de esa entrega, y el id que ves en GET /webhooks/{id}/deliveries. |
X-WAT-Attempt | Número de intento de esta entrega, empezando en 1. |
X-WAT-Timestamp | Segundos Unix en el momento de firmar. |
X-WAT-Signature | v1= + HMAC-SHA256 hex de timestamp + "." + cuerpo. Durante el solape de una rotación de secreto van dos, separadas por coma. Ver Firmas. |
Content-Type | application/json. |
User-Agent | WAT-Webhooks/1. |
json
{
"id": "6d2e1f0a-9b8c-4d7e-a6f5-4c3b2a1d0e9f",
"type": "booking.completed",
"version": 1,
"created_at": "2026-09-18T10:42:07.318Z",
"organization": {
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
},
"resource": {
"type": "booking",
"id": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f"
},
"changes": [
"status"
],
"previous_status": "en_curso",
"data": {
"id": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"reference": "10031234",
"external_id": "VLC-2026-000123",
"status": "completed",
"service_type": "transfer",
"hours": null,
"pickup_at": "2026-09-18T08:30:00+00:00",
"end_at": null,
"pickup": {
"address": "Aeropuerto de Valencia (VLC)",
"lat": 39.4893,
"lng": -0.4816
},
"destination": {
"address": "Hotel Las Arenas, Valencia",
"lat": 39.4735,
"lng": -0.3245
},
"distance_km": 12.4,
"flight": {
"number": "VY1234",
"pending": false
},
"passengers": {
"count": 2,
"luggage": 2,
"luggage_big": 2,
"luggage_small": 0,
"baby_seats": 0,
"child_seats": 0,
"booster_seats": 0,
"wheelchair": false
},
"vehicle_category": "sedan",
"customer": {
"id": "7e6d5c4b-3a2b-4c1d-9e8f-7a6b5c4d3e2f",
"name": "Hotel Las Arenas"
},
"assignment": {
"external": false,
"driver": {
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"name": "Marta Gil",
"nickname": "Marta"
},
"vehicle": {
"id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"plate": "1234 KLM",
"model": "Mercedes Clase E"
}
},
"role": "originator_and_executor",
"channel": "customer_engine",
"confirmation": {
"mode": "auto",
"confirmed_at": "2026-09-17T18:02:11+00:00"
},
"event_reference": null,
"created_at": "2026-09-17T18:02:11.204Z"
}
}| Campo | Qué es |
|---|---|
id | Id de la entrega (igual que X-WAT-Delivery). |
type · version | Tipo de evento y versión del cuerpo (1). |
created_at | Cuándo ocurrió el evento. |
organization.id | Tu organización. |
resource | type e id del recurso (booking; webhook en el evento de prueba). |
changes | Columnas internas que cambiaron. Las del pasajero solo con passengers:read. |
previous_status | Estado interno anterior (confirmada, en_curso…). |
data | La reserva completa, con la forma de Booking y según tus scopes. null en el evento de prueba. |
Qué tiene que hacer tu servidor
- Leer el cuerpo crudo (bytes, sin parsear) y verificar la firma.
- Rechazar timestamps con más de 5 minutos de diferencia.
- Responder
2xxen menos de 10 segundos. Encola el trabajo pesado y responde ya. - Deduplicar por
X-WAT-Event-Id. - Si necesitas más de lo que trae
data, llamar aGET /v1/bookings/{id}.
Redirecciones y 3xx
No se siguen. Un 301/302 cuenta como fallo permanente. Apunta directamente a la URL final.
Activación
Los webhooks (y la cola de eventos) se activan por organización, junto con la API. Hasta entonces puedes crear los destinos y probarlos con un evento de prueba.