DovExpress — Guía de Integración API Pública & Webhook (ES)
Audiencia: socios/clientes externos que integran con DovExpress para inyección de órdenes, tracking y prueba de entrega (POD). Última revisión contra el código fuente:
api/src(controladoresapi.ts,orders.ts,clients.ts,status.ts; serviciowebhooks.ts).
Todo lo de abajo está en producción salvo que se marque explícitamente como [PROPUESTO] (una brecha planificada — no dependas de ello todavía).
Contenido
- Resumen
- Inicio rápido
- Autenticación
- Endpoints
- Webhooks
- Errores
- Catálogo de estados
- Zona horaria
- Prueba de entrega (POD)
- Ciclo de vida de estados & roadmap
- Referencia rápida
1. Resumen
DovExpress expone una pequeña API REST para clientes. Un cliente es un registro en la tabla Clients identificado por un api_key único. Como cliente podés:
- Crear órdenes de entrega.
- Consultar una orden (por id interno, o por tu referencia / código DovExpress) con su historial completo de tracking.
- Recibir un
POSTpor webhook en cada cambio de estado, una vez que registres una URL de webhook.
El ciclo de vida de la orden se rige por un catálogo de estados compartido (44 estados en producción). Los cambios de estado los producen las operaciones de DovExpress (bodega, distribuidor, app del conductor, panel admin) y se te envían en tiempo real.
Vos ──POST /api/orders/create──▶ DovExpress
│
las operaciones mueven la orden entre estados
│
Tu webhook ◀──POST {data}───────────┘ (cambio de estado, incl. POD en Entregado)2. Inicio rápido
Una integración funcional son cuatro pasos:
- Obtené tu
api-keyde DovExpress (tu única credencial — mantenela secreta). - Registrá tu webhook para recibir actualizaciones de estado:bash
curl -X PUT https://api.dovexpresscr.com/api/webhook \ -H "api-key: $DOV_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://tu-app.example.com/dovexpress/webhook" }' - Creá una orden:bash
curl -X POST https://api.dovexpresscr.com/api/orders/create \ -H "api-key: $DOV_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reference_id": "CR0256301601", "contactDetails": { "contactName": "Juan Perez", "contactPhone": "88888888" }, "addressDetails": { "address": "200m norte de la iglesia", "postalCode": "10203" } }' - Seguila — esperá los eventos del webhook, o consultá:bash
curl https://api.dovexpresscr.com/api/orders/reference/CR0256301601 \ -H "api-key: $DOV_API_KEY"
Sincronizá tu catálogo de estados una vez al arrancar con
GET /api/statuses(§4.4) y mapeá por el código DOV — nunca hardcodees códigos numéricos.
3. Autenticación
URL base de producción:
https://api.dovexpresscr.comCredencial: toda solicitud debe enviar el header:
api-key: <TU_API_KEY_DE_CLIENTE>
La API resuelve el cliente a partir del api-key. Una clave faltante o desconocida devuelve 401 Unauthorized. No hay OAuth/JWT para clientes — el api-key es la única credencial. Mantenela del lado del servidor y en secreto; nunca la expongas en código de navegador o móvil.
Todos los endpoints están acotados a tu cliente: solo podés leer y escribir tus propias órdenes.
4. Endpoints
4.1 Crear orden — POST /api/orders/create
Campos de la solicitud
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
reference_id | string | – | Tu referencia de tracking. Única por cliente (reuso → 409). |
contactDetails.contactName | string | ✅ | Nombre del destinatario. |
contactDetails.contactPhone | string | – | Teléfono principal. |
contactDetails.contactPhone2 | string | – | Teléfono secundario. |
addressDetails.address | string | ✅ | 1..2000 caracteres. |
addressDetails.postalCode | string | ✅ | |
addressDetails.state / region / city / country | string | – | |
addressDetails.lat / lng | number | – | Coordenadas para ruteo. |
addressDetails.notes | string | – | Notas de entrega para el conductor. |
packageDetails.product | string | – | "Nombre - cant", o separado por comas "A - 1, B - 2". |
packageDetails.quantity | number | – | |
packageDetails.products[] | array | – | Alternativa estructurada: { product, name, quantity }. |
cod | number | – | Monto contra entrega (cash-on-delivery). |
notes | string | – | Notas a nivel de orden. |
Cuerpo de ejemplo
{
"reference_id": "CR0256301601", // opcional; única por cliente
"contactDetails": {
"contactName": "Juan Perez", // requerido
"contactPhone": "88888888",
"contactPhone2": "70000000"
},
"addressDetails": {
"address": "200m norte de la iglesia", // requerido (1..2000 caracteres)
"state": "San José",
"region": "Central",
"city": "Escazú",
"country": "Costa Rica",
"postalCode": "10203", // requerido
"lat": 9.9281,
"lng": -84.0907,
"notes": "Llamar antes"
},
"packageDetails": {
"product": "Shoes - 1", // "Nombre - cant", o "A - 1, B - 2"
"quantity": 1,
"products": [
{ "product": "SKU123", "name": "Shoes", "quantity": 1 }
]
},
"cod": 25000,
"notes": "Frágil"
}Comportamiento
- Las órdenes nuevas inician en el estado
Created(Creado). reference_idse deduplica por cliente: reenviar la misma referencia devuelve409 Conflict.- DovExpress genera el
codeinterno de la orden (ej.DOV_218345) — es el número de tracking que se muestra en el portal. Guardalo junto a tureference_id.
4.2 Obtener orden por id interno — GET /api/orders/:id
4.3 Obtener orden por referencia o código — GET /api/orders/reference/:reference
:reference coincide con tu reference_id o con el code de DovExpress. Tanto 4.2 como 4.3 están acotados a tu cliente y devuelven:
{
"data": {
"id": "uuid",
"code": "DOV_218345", // código de orden/tracking de DovExpress
"reference_id": "CR0256301601",
"contactDetails": { "contactName": "...", "contactPhone": "...", "contactPhone2": "..." },
"addressDetails": { "address": "...", "state": "...", "region": "...", "city": "...",
"country": "...", "postalCode": "...", "lat": 0, "lng": 0, "notes": "..." },
"packageDetails": { "product": "...", "quantity": "..." },
"cod": 25000,
"currentStatus": {
"code": 5014, "statusId": "uuid", "statusName": "Delivered", "statusNameEs": "Entregado",
"createdAt": "2026-02-11T17:38:58.000Z",
"pod": [ { "type": "photo", "url": "https://dovexpress.s3.amazonaws.com/uploads/photos/..." } ]
},
"pod": [ { "type": "photo", "url": "https://dovexpress.s3.amazonaws.com/uploads/photos/..." } ],
"trackingHistory": [
{ "code": 5001, "statusId": "uuid", "statusName": "Created", "statusNameEs": "Creado", "createdAt": "...", "pod": [] },
{ "code": 5025, "statusId": "uuid", "statusName": "In Transit", "statusNameEs": "En Tránsito", "createdAt": "...", "pod": [] },
{ "code": 5014, "statusId": "uuid", "statusName": "Delivered", "statusNameEs": "Entregado", "createdAt": "...",
"pod": [ { "type": "photo", "url": "https://dovexpress.s3.amazonaws.com/uploads/photos/..." } ] }
]
}
}Cada ítem de trackingHistory y el currentStatus incluyen el código DOV (code), los nombres en inglés (statusName) y español (statusNameEs), la marca de tiempo (createdAt), y un arreglo pod con las URLs de prueba de entrega de ese evento (poblado, p. ej., en Entregado). El objeto de la orden también lleva un pod de nivel superior (el POD del evento actual) y code (el código de orden/tracking de DovExpress).
4.4 Listar catálogo de estados — GET /api/statuses
Devuelve el catálogo compartido completo. Llamalo una vez al arrancar (y periódicamente) para mantener tu mapeo sincronizado, incluyendo cualquier estado que DovExpress agregue después:
curl https://api.dovexpresscr.com/api/statuses -H "api-key: $DOV_API_KEY"{
"data": [
{ "code": 5014, "name": "Delivered", "name_es": "Entregado", "is_final": true, "requires_photo": true, "requires_signature": false },
{ "code": 5025, "name": "In Transit", "name_es": "En Tránsito", "is_final": false, "requires_photo": false, "requires_signature": false }
]
}code es el código DOV (ver §7). Mapeá tus estados internos contra él.
4.5 Gestión self-service del webhook — GET / PUT / DELETE /api/webhook
Gestioná tu propio endpoint de webhook con tu api-key — sin intervención de DovExpress:
| Método | Efecto |
|---|---|
GET /api/webhook | Devuelve { "data": { "url": "...", "updatedAt": "..." } }, o { "data": null } si no hay. |
PUT /api/webhook | Cuerpo { "url": "https://tu-endpoint/hook" } → registra o actualiza (upsert; sobrescribe la URL anterior). |
DELETE /api/webhook | Elimina tu webhook. |
La relación es una URL de webhook por cliente (ClientsWebhooks, client_id único).
5. Webhooks
5.1 Configuración
El webhook es self-service vía tu api-key (§4.5): PUT para registrar/actualizar, GET para leer, DELETE para eliminar. Un admin de DovExpress también puede registrarlo por vos. Una URL de webhook por cliente; PUT hace upsert.
5.2 Entrega & payload
En cada cambio de estado, DovExpress envía un POST HTTP a tu URL con:
- Header
Authorization: Basic <token fijo>— el token que DovExpress te emitió. - Cuerpo JSON con forma
{ "data": <payload> }.
Hay dos formas de payload, según el flujo que disparó el cambio:
A) Actualización de un solo estado (rica — la mayoría de los eventos):
{
"data": {
"id": "uuid",
"code": "DOV_218345",
"reference_id": "CR0256301601",
"contactDetails": { "...": "..." },
"addressDetails": { "...": "..." },
"packageDetails": { "product": "...", "quantity": "..." },
"cod": 25000,
"currentStatus": { "code": 5014, "statusId": "uuid", "statusName": "Delivered", "statusNameEs": "Entregado", "createdAt": "...",
"pod": [ { "type": "photo", "url": "https://dovexpress.s3.amazonaws.com/uploads/photos/..." } ] },
"trackingHistory": [ { "code": 5001, "statusId": "...", "statusName": "...", "statusNameEs": "...", "createdAt": "...", "pod": [] } ]
}
}B) Operaciones masivas (mínima — ej. asignación masiva / carga recibida):
{ "data": { "orderId": "uuid", "statusId": "uuid", "userId": "uuid" } }⚠️ El payload masivo (B) lleva solo UUIDs internos — sin referencia, sin nombre/código de estado, sin POD. Cuando lo recibás, resolvé los detalles llamando a
GET /api/orders/:id(§4.2) y mapeandostatusIdcontra tu caché deGET /api/statuses(§4.4).
5.3 ¿Prueba de entrega en el webhook? — SÍ ✅
El trackingHistory del payload lleva un arreglo pod por evento, y el objeto de la orden expone un pod de nivel superior para el evento actual. En Entregado, este contiene la(s) URL(s) de la foto de prueba de entrega (y la firma cuando está presente), así que recibís el POD automáticamente — sin polling. El mismo enriquecimiento aplica a los endpoints GET de orden (§4.2–4.3).
5.4 Manejo del webhook — recomendaciones
- Verificá antes de confiar: chequeá que el header
Authorization: Basiccoincida con el token que DovExpress te emitió; rechazá cualquier otra cosa con401. - Confirmá rápido: devolvé HTTP
2xxapenas hayas persistido el evento; hacé el trabajo pesado de forma asíncrona para no bloquear a DovExpress en tu procesamiento. - Sé idempotente: el mismo evento puede llegar más de una vez. Deduplicá con una clave estable — ej.
id+statusId+createdAt— y hacé que reprocesar sea un no-op. - Manejá ambas formas: ramificá según el payload (rico con
currentStatusvs. mínimo conorderId/statusId/userId) y enriquecé la forma B vía la API como en §5.2.
6. Errores
La API usa códigos de estado HTTP convencionales. Los que conviene manejar explícitamente:
| Código | Cuándo | Qué hacer |
|---|---|---|
401 Unauthorized | api-key faltante o desconocida (o Authorization del webhook que no coincide). | Revisá la credencial/header. |
400 Bad Request | Cuerpo inválido — falta un campo requerido, o address fuera de 1..2000 caracteres. | Corregí el payload antes de reintentar. |
409 Conflict | reference_id ya usada por tu cliente. | Tratalo como "ya creada"; no reintentes a ciegas. |
404 Not Found | El id/referencia de la orden no existe o no es tuya. | Verificá el identificador; las órdenes están acotadas al cliente. |
5xx | Error transitorio del servidor. | Reintentá con backoff; las entregas de webhook que no logres confirmar con 2xx reconciliálas con polling. |
Tratá los
4xxcomo error tuyo (corregí y reintentá deliberadamente) y los5xx/errores de red como transitorios (reintentá con backoff exponencial). Para seguridad idempotente en creaciones, usá tureference_idcomo clave.
7. Catálogo de estados
Todos los clientes comparten un único catálogo Status. Producción tiene actualmente 44 estados.
No hardcodees códigos. Cada estado tiene un código DOV — un entero propio de DovExpress, autoasignado y estable (
MAX+1, empezando en5001; los códigos existentes nunca cambian). Obtené el catálogo en vivo desdeGET /api/statuses(§4.4) y mapeá contracode. Los nombres de abajo son referencia humana; elcode/name/name_esautoritativo siempre viene de la API.
7.1 Estados hito principales
| name (EN) | name_es | Final |
|---|---|---|
| Created | Creado | |
| Second Attempt | Segundo Intento | |
| First attempt — Consignee Moved | 1er Intento Cambió Domicilio | |
| First attempt — Consignee Not Available | 1er Intento Destinatario No Disponible | |
| First attempt — Refused by Consignee | 1er Intento Rehusado por Destinatario | |
| First attempt — Inaccessible Delivery Zone | 1er Intento Fuera de Cobertura | |
| First attempt — Incorrect Address | 1er Intento Dirección Errónea | |
| First attempt — Unknown Consignee | 1er Intento Destinatario Desconocido | |
| 2nd attempt — Consignee Not Available | 2do Intento Destinatario No Disponible | |
| 2nd attempt — Incorrect Address | 2do Intento Dirección Incorrecta | |
| 2nd attempt — Refused by Consignee | 2do Intento Rehusado por Destinatario | |
| 2nd attempt — Unknown Consignee | 2do Intento Destinatario Desconocido | |
| Delivered | Entregado | ✅ |
7.2 Estados operativos / internos
Assigned to Distributor, Assigned to Driver, In Transit, Cargo Received, Received by Distributor, Accepted, First Attempt, Absent, Cannot Locate, Incorrect Phone, No Money, Does Not Want It, Bought Other Product, Will Buy Later, Other Promotion, To Rescue, Already Received the Package, Duplicate Package, Rescheduled, Wrong Address, Wrong Price, Wrong Product, Out of Coverage, Did Not Request Anything, Returned to Sender (final), Returned to Warehouse (DOV), Call Center Time, Out of the country, Hospitalized, Package in Warehouse, Rejected.
Estados finales: solo Delivered (Entregado) y Returned to Sender (Devuelto al remitente). Una vez que una orden es final, su estado ya no puede cambiar. Foto requerida al asignar: Delivered, Duplicate Package, Rescheduled.
8. Zona horaria
El proceso de la API corre con TZ='America/Costa_Rica' (UTC−6, sin horario de verano). Las marcas de tiempo de los eventos de tracking se gestionan y muestran en hora de Costa Rica. Las marcas serializadas en JSON son ISO-8601; convertí a America/Costa_Rica para mostrarlas en local.
9. Prueba de entrega (POD)
- En Entregado, la app del conductor captura una foto obligatoria (y una firma donde el estado lo requiere). Los archivos se guardan en S3 como
OrderFilesconfile_typephotoosignature. - Formato de URL pública:
https://dovexpress.s3.amazonaws.com/uploads/photos/<uuid>-<timestamp> - Las URLs de POD se te entregan automáticamente en el evento de webhook
Delivered(arreglopod, §5.3) y también están disponibles bajo demanda vía los endpoints GET de orden (§4.2–4.3).
10. Ciclo de vida de estados & roadmap
Cuando un admin de DovExpress agrega un nuevo estado, se le asigna automáticamente el siguiente código DOV libre (MAX+1, empezando en 5001) — único y estable, así los códigos existentes nunca se mueven. El nuevo estado aparece de inmediato en GET /api/statuses. Te mantenés sincronizado consultando ese endpoint; como los códigos son estables, un simple diff contra tu catálogo guardado revela las novedades.
Entregado en esta integración:
- ✅ Endpoint de catálogo de estados —
GET /api/statuses(§4.4). - ✅ Payload de webhook enriquecido y consistente en todos los flujos (individual + masivo), incluyendo
code,reference_id,statusName,statusNameEs,createdAt. - ✅ POD en el webhook en
Delivered— URL(s) de archivo adjuntas automáticamente (§5.3). - ✅ Gestión self-service del webhook —
GET/PUT/DELETE /api/webhookcon upsert (§4.5).
Opcional / futuro [PROPUESTO]:
- Evento push
status.created— notificar proactivamente a los clientes integrados cuando se agrega un nuevo estado (hoy lo detectás consultandoGET /api/statuses).
11. Referencia rápida
| Capacidad | Estado |
|---|---|
Crear orden (POST /api/orders/create) | ✅ Implementado |
| Obtener orden por id / referencia | ✅ Implementado |
| Marcas de tiempo de Costa Rica (UTC−6) | ✅ Implementado |
| Webhook en cambio de estado (por cliente) | ✅ Implementado |
| Payload rico y consistente (individual + masivo) | ✅ Implementado |
| Imágenes POD en webhook / API | ✅ Implementado |
Endpoint de catálogo de estados (GET /api/statuses) | ✅ Implementado |
Gestión self-service del webhook (GET/PUT/DELETE /api/webhook) | ✅ Implementado |
| Códigos DOV (auto, únicos, estables) | ✅ Implementado |
Notificación push de nuevo estado (status.created) | ❌ Opcional / futuro |
Chuleta de endpoints — todos requieren el header api-key, base https://api.dovexpresscr.com:
| Método | Ruta | Propósito |
|---|---|---|
POST | /api/orders/create | Crear una orden |
GET | /api/orders/:id | Obtener orden por id interno |
GET | /api/orders/reference/:reference | Obtener orden por tu referencia o código DOV |
GET | /api/statuses | Listar el catálogo de estados |
GET | /api/webhook | Leer tu URL de webhook |
PUT | /api/webhook | Registrar / actualizar tu URL de webhook |
DELETE | /api/webhook | Eliminar tu webhook |