Skip to main content
Una app de envío le permite a un comercio ofrecer tu servicio de logística: tu app cotiza el envío en el checkout, crea y cancela los envíos cuando el comercio lo pide, y avisa cómo avanza la entrega. La integración es HTTP en los dos sentidos:
  • Commercy te llama a tres rutas de tu servidor (cotizar, crear y cancelar un envío). Cada request va firmado.
  • Vos llamás a la API pública para informar el seguimiento de la entrega.
No guardamos credenciales de tu servicio de logística: tu app identifica al comercio por la instalación y resuelve su cuenta con tu carrier en tu propio proceso de alta.

Cómo se habilita

  1. Pedí en tu app el scope write_orders (incluye read_orders). Sin read_orders no te mandamos cotizaciones ni altas, porque llevan datos del comprador. Para informar el seguimiento necesitás write_orders. Ver Scopes y permisos.
  2. Guardá el secreto de webhook de tu app (el whsec_... que ves en el portal): es con el que firmamos estos requests, el mismo de los webhooks. Tu app no necesita recibir eventos para ser carrier.
  3. Pedile a soporte que habilite tu app como carrier. Pasale el id de tu app (el de la URL de tu app en el portal) y la URL base de tu API (apiBaseUrl): tiene que ser https y pública, por ejemplo https://api.tuapp.com. Esa base se concatena con las rutas de esta página.
  4. El comercio instala la app. Desde ese momento la ofrecemos en todas las tiendas de ese comercio, siempre que su plan incluya las apps. Desinstalar la app es el interruptor para apagarla.
Mientras tu app esté en borrador, la habilitamos sólo en la tienda de desarrollo de tu cuenta, para que pruebes el flujo completo.

Cómo te llamamos

Todas las operaciones son un POST con cuerpo JSON a <apiBaseUrl><ruta>.
  • Usamos https hacia una dirección pública. No seguimos redirects.
  • Leemos como máximo 64 KB de tu respuesta. Si es más larga la tratamos como error, nunca como JSON parcial.
  • Tiempos máximos: 3 segundos para cotizar y 15 segundos para crear o cancelar un envío.
  • Respondé 2xx con un cuerpo JSON. Cualquier otro código, un timeout o un JSON inválido cuentan como error.

Headers

El cuerpo de todas las operaciones lleva installation_id y shop_id (la sucursal de la tienda, el mismo id que devuelve GET /shops).

Verificar la firma

Es el mismo esquema que los webhooks: HMAC-SHA256(secreto, "<X-Commercy-Timestamp>.<cuerpo crudo>") en hexadecimal y con el prefijo v1=, con tu secreto completo (incluido whsec_). Verificala siempre, porque cualquiera podría llamar a tus rutas:
  1. Leé el cuerpo crudo, antes de parsear el JSON.
  2. Comparala con X-Commercy-Signature en tiempo constante.
  3. Rechazá timestamps que difieran más de 5 minutos de tu reloj.
  4. Comprobá que el installation_id del cuerpo sea el de X-Commercy-Installation. La firma cubre el cuerpo, no la URL, así que el recurso viaja en el cuerpo.
Con Express, usá el parser raw en esas rutas para conservar el cuerpo crudo:

Cotizar un envío

Cuando el comprador carga su código postal en el checkout, te pedimos las opciones de envío.
  • postal_code es el código postal de 4 dígitos que cargó el comprador.
  • items trae un elemento por producto del carrito, con su quantity. weight (g o kg) y dimensions (cm o m) son los del producto y faltan si el producto no los tiene. items puede venir vacío (por ejemplo, cuando se cotiza desde el bot de WhatsApp): devolvé igual tus opciones.
  • No mandamos valor declarado ni dirección de origen. Si los necesitás, leelos con la API (read_shops, read_orders).
Respondé:
Cómo tratamos tu respuesta:
  • Un método que no cumple las reglas se descarta, y también uno repetido (mismo method_id). Los demás se ofrecen igual. Una respuesta sin la lista methods cuenta como error.
  • El precio que vio el comprador es el que se cobra. Guardamos tu cotización 15 minutos para ese carrito, tienda y código postal, y el confirmar la compra usa esa misma cotización. Sólo recotizamos si ya venció. Si al confirmar tu app ya no ofrece ese method_id, la compra se rechaza: nunca se cobra un envío de $0.
  • Tu app puede quedar fuera del checkout por un rato. Si no respondés a tiempo, la conexión falla o devolvés un 5xx, dejamos de ofrecerte durante aproximadamente 1 minuto y el checkout sigue sin tu app. Un 4xx o una respuesta inválida no te saca: sólo esa cotización no aparece.
  • El checkout nunca espera a tu app más de 3 segundos.

Crear un envío

El comercio crea el envío desde la venta en Commercy («Enviar con tu app»). Sólo se puede cuando la venta es un envío a domicilio con dirección completa (calle y código postal) y no tiene ya otro envío vigente.
  • idempotency_key es obligatoria y tenés que respetarla. Tiene el formato <id de la venta>:<n>. La key identifica un intento de envío, no la venta: si no te llega respuesta (por ejemplo un timeout) y el comercio reintenta, la key es la misma y tenés que devolver el mismo envío, sin crear otro. Cuando un envío se cancela o se desvincula, el siguiente alta de esa venta trae una key nueva (:2, :3…) y ahí sí creás un envío nuevo.
  • method_id es el método que eligió el comprador si la venta se cotizó con tu app. Si la venta se cotizó con otro carrier o se cargó a mano viene null: usá tu servicio por defecto.
  • Los datos que no tenemos vienen null (number, sku, name, apartment, notes, phone, email…). El email del destinatario sólo está en las ventas de invitados.
  • items[].weight y items[].dimensions siguen el mismo formato que en la cotización y faltan si el producto no los tiene.
Respondé:
Una respuesta que no cumple las reglas es un error: el comercio ve un aviso y puede reintentar. Como puede que ya hayas creado el envío, el reintento trae la misma idempotency_key y tenés que devolver el mismo code. Si el envío quedó creado en tu sistema pero Commercy no pudo guardarlo, le mostramos el code al comercio para que te avise. Guardamos el code y el seguimiento (si lo devolviste) en la venta.

Cancelar un envío

Respondé { "canceled": true }. Cualquier otra respuesta, un error o un timeout dejan el envío vigente en Commercy y el comercio puede reintentar. Cancelar sólo se puede mientras tu app siga instalada.

Informar el seguimiento

No te consultamos el estado de los envíos: lo empujás vos con la API pública, con el token de acceso de la instalación y el scope write_orders. El order.id del alta es el id de la venta en la API.
  • POST /orders/:id/status con { "status": "shipped" } cambia el estado de la venta. Los estados son confirmed, preparing, shipped y delivered, y tienen que existir en el flujo de estados de la tienda.
    • delivered cierra la venta: descuenta stock, mueve la caja y genera el asiento contable. Mandalo sólo cuando el paquete se entregó.
    • Una venta cancelada, rechazada o devuelta no se reabre: responde 409 conflict con details[0].reason = "order_closed".
    • Si la entrega exige confirmar un cobro contra entrega, responde 409 conflict con details[0].reason = "payment_confirmation_required": esa confirmación la hace el comercio en Commercy.
  • POST /orders/:id/fulfillment con { "tracking_number": "...", "tracking_url": "..." } carga o actualiza el seguimiento de la venta. Si cambió el número o el link, usalo para corregirlos. El tracking_url puede ser http o https.
Los dos emiten order.updated. Ver los errores posibles en Errores.

Qué pasa con la venta

  • Si el comercio cancela la venta, no cancelamos el envío en tu sistema. Escuchá el webhook order.cancelled y cancelalo vos.
  • Si el comercio desinstala tu app, sus envíos vigentes quedan desvinculados en Commercy sin llamar a tu app (recibís el webhook app.uninstalled). El comercio ve el aviso para gestionarlos directamente con tu servicio.
  • Si la instalación queda revocada (por ejemplo, porque se reutilizó un refresh token), los envíos no se desvinculan: el comercio sigue viendo el aviso de envío vigente y no puede cancelarlo desde Commercy hasta que reinstale la app.

Checklist de salida

  • Verificás la firma sobre el cuerpo crudo y que installation_id coincida con el header.
  • Cotizás en menos de 3 segundos y con price numérico.
  • El alta devuelve el mismo code para la misma idempotency_key.
  • Cancelás el envío cuando recibís order.cancelled.
  • Empujás shipped, delivered y el seguimiento con la API.

Ejemplo

shipping-carrier es una app de envío de ejemplo, lista para copiar y adaptar.