Guía: sincronizar servicios completados
Dos caminos que se complementan: el webhook booking.completed para el instante, y GET /events + external IDs para no perder ninguno.
El instante: webhook
Suscribe un webhook a booking.completed (y a booking.cancelled y booking.no_show si tu sistema los distingue). Cada uno trae la reserva completa en data con tu external_id si ya lo guardaste.
// tras verificar la firma (ver Firmas) y deduplicar por evento.id…
switch (evento.type) {
case 'booking.completed':
await marcarFacturable({ watId: evento.resource.id, externalId: evento.data.external_id, reserva: evento.data });
break;
case 'booking.cancelled':
case 'booking.no_show':
await marcarNoFacturable({ watId: evento.resource.id, motivo: evento.type });
break;
}Una empresa puede corregir un estado (por ejemplo, pasar una reserva de completed a cancelled). Llega como booking.cancelled con previous_status: "completada". Trata previous_status como parte del mensaje.
La red de seguridad: eventos
GET /v1/events es la lista de todo lo que pasó, sin datos personales, del más reciente al más antiguo. Guarda el id del último evento que procesaste y, en un cron (cada hora vale), recorre hasta encontrarlo:
async function reconciliar(ultimoProcesado) {
const nuevos = [];
let cursor = null;
bucle: do {
const q = new URLSearchParams({ type: 'booking.completed', limit: '100', ...(cursor ? { cursor } : {}) });
const page = await (await fetch(`${API}/events?${q}`, { headers: h })).json();
for (const ev of page.data) {
if (ev.id === ultimoProcesado) break bucle;
nuevos.push(ev);
}
cursor = page.next_cursor;
} while (cursor);
for (const ev of nuevos.reverse()) { // del más antiguo al más nuevo
if (await yaProcesado(ev.id)) continue; // el webhook llegó primero
const b = await (await fetch(`${API}/bookings/${ev.resource.id}`, { headers: h })).json();
await marcarFacturable({ watId: b.id, externalId: b.external_id, reserva: b });
await recordar(ev.id);
}
}| Situación | Quién la cubre |
|---|---|
| Todo va bien | El webhook, en segundos. |
| Tu servidor estuvo caído 20 minutos | Los reintentos del webhook (hasta ~1 h). |
| Tu servidor estuvo caído medio día | La reconciliación por eventos. |
| Procesaste el evento pero no guardaste el resultado | Deduplicación por evento.id en tu lado + reconciliación. |
| No sabes desde cuándo | GET /bookings?status=completed&pickup_from=… y external_id: null como marca de «pendiente». |
Cerrar el círculo con tu id
En cuanto creas el documento en tu sistema, guarda su número en WAT con PUT /bookings/{id}/external-id. Así una reserva con external_id: null significa exactamente «todavía no la he procesado», y la reconciliación de fin de mes es un filtro, no una comparación de tablas. Ver Identificadores externos.