Guía
Eventos durables y webhooks firmados
Cada cambio relevante produce un evento con objetivo de retención de 90 días; la eliminación TTL es eventual. Los webhooks se entregan al menos una vez: deduplica siempre.
Suscribirse
Registra hasta 20 URLs públicas HTTPS en el puerto 443 y hasta 30 tipos por suscripción. No se aceptan direcciones privadas, loopback, link-local ni destinos que resuelvan hacia ellas. Usa tipos específicos o *. El secreto whsec_… se muestra al crear o rotar y la rotación entra en vigor inmediatamente.
Verificar una entrega
Moku envía Moku-Event-Id, Moku-Delivery-Id y Moku-Signature: t=<unix>,v1=<hex>. Calcula HMAC-SHA256 con el secreto completo sobre timestamp + "." + cuerpo_crudo, compara en tiempo constante y rechaza timestamps antiguos.
Verifica la firma sobre el cuerpo crudo recibido, antes de JSON.parse. Este módulo para Node.js 18 o posterior compara en tiempo constante y rechaza firmas con más de 5 minutos de diferencia. Responde 401 cuando devuelva false.
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 300;
export function verifyMokuSignature(rawBody, signatureHeader, signingSecret, now = Date.now()) {
const fields = Object.fromEntries(
String(signatureHeader ?? '').split(',').map(part => part.trim().split('=', 2)),
);
const timestamp = Number(fields.t);
if (!Number.isSafeInteger(timestamp) || !/^[0-9a-f]{64}$/.test(fields.v1 ?? '')) return false;
if (Math.abs(now / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
const expected = createHmac('sha256', signingSecret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
return timingSafeEqual(expected, Buffer.from(fields.v1, 'hex'));
}El cuerpo del evento contiene metadatos del recurso, no su snapshot completo; consulta el endpoint del recurso para obtener el estado actual.
Entrega y reintentos
- Responde cualquier
2xxdentro de 5 segundos. - Cada destino se procesa de forma independiente. Una URL lenta o fallida no obliga a repetir entregas exitosas a otros destinos.
- Los fallos se reintentan hasta 10 intentos con backoff desde 30 segundos y tope de una hora.
- Una reproducción manual puede generar duplicados y no usa clave de idempotencia.
- Deduplica por
event.id;Moku-Delivery-Ides estable por evento y suscripción, incluso al reproducir. - Las entregas pueden llegar fuera de orden; usa la versión del recurso y consulta su estado vigente.
- Ignora campos y tipos futuros desconocidos.
Reproducir y consultar el resultado
POST /vendors/{vendor_id}/events/{event_id}/replay responde 202 Accepted cuando el trabajo quedó guardado, no cuando terminó la entrega. Guarda replay_id y consulta:
GET /vendors/{vendor_id}/events/{event_id}/replays/{replay_id}Necesitas webhooks.write para iniciar y webhooks.read para consultar. El estado avanza entre queued, running, completed o failed. total es null hasta seleccionar los destinos. Los contadores delivered, failed y skipped permiten distinguir resultados parciales; una suscripción eliminada o desactivada se omite.
Cada POST crea una reproducción nueva. Si pierdes la respuesta, no asumas que el primer intento falló: repetirlo puede duplicar entregas.
Leer eventos sin perder ninguno
La lista de eventos recorre todos los eventos confirmados de la tienda en un solo orden de llegada. La primera lectura que ve un evento lo numera después de todos los que ya entregó, así que continuar desde el cursor nunca salta un evento, aunque se haya confirmado tarde.
Es el orden en que Moku descubre los eventos, no el de confirmación ni el de occurred_at. Para ordenar cambios de un mismo recurso, usa resource.version.
GET /vendors/{vendor_id}/events?limit=100&page_after={next_cursor}Guarda siempre page.next_cursor: viene en todas las respuestas, también en las vacías. Si page.has_more es true, pide la página siguiente de inmediato. Si es false, ya estás al día: vuelve a consultar más tarde con ese mismo cursor.
La lectura es al menos una vez y una página puede repetir un evento. Deduplica por id.
GET /vendors/{vendor_id}/events?occurred_after=2026-10-06T12:00:00Zoccurred_after, una fecha y hora RFC 3339 con zona horaria, devuelve solo eventos con occurred_at posterior. Filtra ese orden sin cambiarlo, y una página puede avanzar el cursor sin devolver eventos. El cursor queda ligado al filtro: si cambias occurred_after, empieza de nuevo sin page_after.
Tipos publicados en v1
product.createdproduct.updatedproduct.publishedproduct.unpublishedproduct.archivedproduct.restoredinventory_item.adjustedinventory_item.reservedinventory_item.consumedinventory_item.releasedinventory_item.restockedinventory_item.reference_price_updatedinventory_item.review_requiredinventory_reservation.createdinventory_reservation.renewedinventory_reservation.committedinventory_reservation.releasedinventory_reservation.expiredconnection.createdconnection.updatedconnection.roles_changedconnection.disconnectedconnection.reconnectedchannel_listing.createdchannel_listing.updatedchannel_listing.endedexternal_order.createdexternal_order.updatedexternal_order.reconciliation_requiredexternal_order.terminal_resolvedexternal_order.reservation_renewedseller_order.paidseller_order.fulfillment_updatedseller_order.cancelledwebhook_subscription.createdwebhook_subscription.updatedwebhook_subscription.deletedwebhook_subscription.secret_rotatedsync_job.queuedsync_job.succeededsync_job.failedbulk_operation.queuedbulk_operation.completedintegration_conflict.createdintegration_conflict.updatedintegration_conflict.reopenedintegration_conflict.resolvedstock_effect.createdstock_effect.confirmedstock_effect.blocked