API de socios: enviar pedidos y recibir callbacks

Envío de pedidos (HMAC inbound en hex), callbacks order.created y order.status_changed (HMAC outbound sha256=), endpoints de reconciliación, deduplicación por X-N1-Delivery-Id, reintentos y circuit breaker con auto-recuperación.

En esta guía vas a…

  1. Construir la URL base y el endpoint POST /api/webhooks/partner/orders
  2. Firmar peticiones inbound (socio → N1) en hex, sin prefijo
  3. Validar callbacks outbound (N1 → socio) con sha256= en la cabecera
  4. Interpretar los códigos de respuesta y el ID PARTNER-{identificador}-{external_order_id}

URLs base

Todas las rutas usan el mismo host de tu instancia N1 (donde está alojado el panel). Por ejemplo:

  • https://TU-DOMINIO-N1.com/api/webhooks/partner/orders
  • https://TU-DOMINIO-N1.com/api/webhooks/partner/docs
  • https://TU-DOMINIO-N1.com/api/webhooks/partner/health

Sustituye TU-DOMINIO-N1.com por el dominio real.

Inbound — enviar pedidos (POST /api/webhooks/partner/orders)

Cabeceras obligatorias

  • Content-Type: application/json
  • X-API-Key: clave con formato pk_live_ o pk_test_ (la misma que se muestra al crear la API Key)
  • X-Webhook-Timestamp: Unix en segundos (entero)
  • X-Webhook-Signature: firma HMAC-SHA256 en hex ( sin el prefijo sha256= )

Cómo calcular la firma (inbound)

  1. Cadena firmada: timestamp + "." + cuerpo JSON en bruto — el punto es literal; el cuerpo debe ser exactamente los bytes enviados (mismo orden de claves, espacios y saltos de línea)
  2. HMAC-SHA256(cadena_firmada, webhook_secret) → salida en hexadecimal
  3. Envía ese hex en X-Webhook-Signature
firma = HMAC_SHA256_hex( timestamp + "." + rawBody, webhook_secret )

Ventana de tiempo

X-Webhook-Timestamp debe estar a como máximo unos 5 minutos del reloj del servidor (anti-replay). Las marcas de tiempo fuera de la ventana generan un error de firma o de timestamp.

Respuestas y errores

  • 201: pedido creado con éxito (el cuerpo incluye order_id)
  • 400: contenido inválido (esquema Zod) — corrige los campos indicados en details
  • 401: API Key inválida/inactiva o firma HMAC inválida o timestamp ausente/inválido — revisa el secreto, la cadena firmada y X-Webhook-Timestamp
  • 403: IP no permitida — cuando hay una lista blanca de IP configurada para esa clave (se ajusta vía administrador/API/soporte; no hay pantalla para esto en el panel de API Keys)
  • 409: pedido duplicado — mismo external_order_id para el mismo identificador de socio
  • 429: límite de peticiones — espera y reinténtalo (la respuesta incluye la cabecera retry_after en segundos)

El ID del pedido en N1

El ID interno generado es:

PARTNER-{partnerIdentifier}-{external_order_id}

Usa el mismo external_order_id que enviaste en el JSON para correlacionarlo con el sistema de origen.

Outbound — callbacks (N1 → tu endpoint)

Cuando se crea un pedido o cambia de estado, N1 puede hacer POST a tu callbackUrl (si está configurado). La documentación completa con ejemplos de validación está en GET /api/webhooks/partner/docs y en el prompt de integración (botón del panel de API Keys).

Eventos

  • order.created: se dispara inmediatamente tras una creación correcta (útil como registro auditable más allá de la respuesta HTTP 201). Requiere "created" en callback_events de la API key.
  • order.status_changed: se dispara ante cualquier cambio de estado del pedido, venga de donde venga (operaciones internas, transportista, 17TRACK, sincronizaciones).

La cabecera X-N1-Event indica qué evento se entregó.

Cabeceras + firma

  • X-N1-Signature: sha256= + hex (distinto del inbound, que es solo hex)
  • X-N1-Timestamp: Unix en segundos. Tu validación debe rechazar todo lo que quede fuera de la ventana de 5 minutos.
  • X-N1-Event: order.created u order.status_changed
  • X-N1-Delivery-Id: UUID único — tu código DEBE deduplicar por este valor (TTL ≥ 30 días). La misma entrega puede llegar más de una vez (reintentos, reenvío manual).
  • Contenido firmado: timestamp + "." + rawBody.

Reintentos, dead-letter y circuit breaker

  • Tu endpoint debe responder 2xx. Cualquier otro estado (o un error de red) dispara un reintento: 3 intentos con espera 1min → 5min → dead-letter.
  • Circuit breaker (auto-recuperación): tras 10 fallos consecutivos, N1 deja de enviar durante 30 minutos. Si vuelve a fallar al reabrir, la espera se duplica: 60 min → 120 min → … con un tope de 24 h. Cualquier respuesta 2xx lo reinicia. No requiere acción manual (el botón de reactivación del panel es solo un atajo).

Reconciliación (auto-recuperación del socio)

Si tu endpoint estuvo caído o perdiste callbacks, tres endpoints GET/POST te permiten recuperarte sin pedir ayuda manual. Exigen las mismas 6 capas de seguridad que el POST de creación; para GET, la cadena firmada es timestamp + "." + ruta-de-la-URL-con-query-string.

  • GET /api/webhooks/partner/orders/:external_order_id — estado actual de un pedido.
  • GET /api/webhooks/partner/orders?updated_since=ISO&limit=100&offset=0 — lista los pedidos modificados después de la marca de tiempo (paginado).
  • POST /api/webhooks/partner/orders/:external_order_id/replay-callbacks — vuelve a encolar los últimos N callbacks de ese pedido (cuerpo: { "limit": 5 }, por defecto 5, máximo 20).

Ejemplos completos en /api/webhooks/partner/docs y en el prompt LLM del panel.

Salud del servicio

GET /api/webhooks/partner/health — respuesta pública { "status": "ok", ... } para monitorización. No requiere autenticación.

Limitaciones y panel

El límite de peticiones y la lista blanca de IP existen en la API; si no aparecen en tu panel, considéralos ajustables vía administrador o soporte. No des por hecho botones que no están en la interfaz.

Artículo relacionado

Configuración en el panel (crear clave, secreto, callback): Claves API de socios — panel.