Integraciones: su sistema conectado con Llevu
En «Integraciones» (en el menú, debajo de «Empresa») se crean las llaves con las que el sistema de su empresa (su ERP, su tienda en línea, su sistema de facturación) crea pedidos en Llevu sin cargarlos a mano y consulta cómo van, y los webhooks con los que Llevu le avisa a su sistema lo que pasa. La ven y la usan los dueños y los administradores.
Crear una llave
Sección titulada «Crear una llave»Toque «Crear una llave», escriba quién la va a usar («ERP», «Tienda en línea») y toque «Crear la llave». La llave aparece una sola vez: cópiela con «Copiar la llave» y péguela en la configuración de su sistema. Llevu no la guarda (solo una huella para reconocerla), así que no se puede volver a ver: si se pierde, revóquela y cree otra.
Quien tenga la llave puede crear, ver y cancelar pedidos de su empresa. No la mande por chat ni por correo. Use una llave por sistema: si uno deja de usarse, revoca la suya sin tocar las demás.
En la lista se ve cada llave con su nombre, cuándo se creó, cuándo se usó por última vez y si está vigente. Lo que crea una llave queda en la Actividad a su nombre («ERP (integración)»).
Revocar una llave
Sección titulada «Revocar una llave»Toque «Revocar» en su fila y confirme. Desde la llamada siguiente, el sistema que la usa ya no puede crear pedidos. No se puede deshacer: si hace falta, se crea otra (puede tener el mismo nombre).
Avisos a su sistema (webhooks)
Sección titulada «Avisos a su sistema (webhooks)»En «Avisos a su sistema (webhooks)», toque «Agregar un webhook», escriba la dirección de su sistema (empieza con https:// y tiene que ser pública) y marque qué avisar: un pedido cambió de estado (pasó a una ruta, volvió a pendiente, se entregó, no se entregó o se canceló), una ruta salió o una ruta terminó. Toque «Agregar». Aparece el secreto del webhook una sola vez: cópielo y guárdelo en su sistema, que lo usa para verificar que cada aviso viene de Llevu. Si se pierde, «Cambiar el secreto» genera otro (el anterior deja de valer en el acto). Una empresa tiene hasta 5 webhooks activos.
Cada webhook dice si está activo, cuántos avisos esperan un reintento y cuántos no se pudieron entregar. «Mandar una prueba» le manda un aviso de prueba; «Ver los últimos avisos» muestra los 50 más recientes con su estado (entregado, pendiente o no se entregó), cuántos intentos llevó y por qué falló; uno que no se entregó se puede «Reenviar». «Pausar» deja de mandarle avisos sin borrarlo; «Reanudar» vuelve. Si su sistema no responde con un 2xx, Llevu reintenta a los 1, 5 y 30 minutos, a las 2 y a las 6 horas, y después lo da por no entregado. Los avisos se guardan 30 días. Crear, cambiar o pausar un webhook queda en la Actividad.
Para quien programa la integración
Sección titulada «Para quien programa la integración»La dirección base es la del panel con /api/v1/integraciones/ (por ejemplo, https://app.llevu.com/api/v1/integraciones/). Toda llamada lleva la llave en la cabecera Authorization: Bearer <llave> y JSON.
| Llamada | Qué hace |
|---|---|
GET yo/ |
Probar la conexión: responde la empresa, el nombre de la llave y sus límites |
POST pedidos/ |
Crear un pedido (201) o devolver el que ya tiene ese código (200) |
POST pedidos/lote/ |
Hasta 500 pedidos en una llamada ({"pedidos": [...]}), cada uno como POST pedidos/ |
GET pedidos/?cambiados_desde=… |
Los pedidos que cambiaron, en el orden en que cambiaron, en páginas |
GET pedidos/<código>/ |
El pedido con ese código: su estado y en qué ruta va |
GET pedidos/<código>/comprobante/ |
El comprobante en PDF, cuando el pedido se cerró |
POST pedidos/<código>/cancelar/ |
Cancelarlo mientras no esté entregado ni cerrado |
GET rutas/?fecha=AAAA-MM-DD |
Las rutas de ese día (hoy, si no se dice) con sus paradas en orden |
GET rutas/<número>/recorrido/ |
Por dónde pasó el vehículo de esa ruta |
El cuerpo de POST pedidos/:
| Campo | Obligatorio | Qué es |
|---|---|---|
codigo_externo |
Sí | El código del pedido en su sistema (sin «/»). Con el mismo código no se crea otro: un reenvío devuelve el que ya existe |
cliente.codigo_externo |
Sí | El código del cliente en su sistema: el próximo pedido del mismo cliente lo reusa |
cliente.nombre |
Sí | Se usa la primera vez; después, lo que su empresa corrija en Llevu se respeta |
cliente.telefono, cliente.correo |
No | Solo la primera vez |
direccion.texto |
Sí, salvo con direccion_codigo |
Como se dice en la región. Si el cliente ya tiene esa dirección (mismo texto), se reusa |
direccion_codigo |
No | El código de una dirección del cliente en Llevu (ver Clientes): el pedido va a esa. Si no existe y viene direccion, se crea con ese código; si es de otro cliente, es error |
direccion.referencia, direccion.latitud, direccion.longitud |
No | Con latitud y longitud, la dirección nueva queda con su pin |
tipo |
No | entrega (por defecto) o recogida |
prioridad |
No | baja, normal o alta; si no viene, la del cliente |
fecha, ventana_desde, ventana_hasta |
No | El día (por defecto, hoy) y el horario de entrega; sin horario, el de la dirección si lo tiene |
recibe_nombre, recibe_telefono, recibe_correo |
No | Quién recibe |
descripcion, bultos, peso_kg, volumen_m3 |
No | Qué lleva |
monto_a_cobrar, moneda, metodo_de_cobro |
No | El cobro contra entrega, con su moneda (CRC, USD…) |
notas_para_el_conductor |
No | Lo que ve el conductor en la app |
campos_propios |
No | {clave: valor} de los campos propios de su empresa |
Un ejemplo:
curl -X POST https://app.llevu.com/api/v1/integraciones/pedidos/ \ -H "Authorization: Bearer <llave>" -H "Content-Type: application/json" \ -d '{"codigo_externo": "SO-1001", "cliente": {"codigo_externo": "C-1", "nombre": "Ferretería La Esquina"}, "direccion": {"texto": "San Pedro, 200 m al norte de la iglesia"}, "bultos": 3, "monto_a_cobrar": "12500.00", "moneda": "CRC"}'La respuesta: {"id", "numero", "codigo_externo", "estado", "fecha", "ruta", "creado"}. numero es el número del pedido en Llevu; ruta, el número de su ruta (o null); creado es false si ese código ya tenía su pedido. Los estados: pendiente, en_ruta, entregado, fallido y cancelado.
Los errores llegan con {"error": {"codigo", "mensaje", "detalles"}}: 401 sin_llave (falta, no vale o está revocada), 422 datos_invalidos (el campo en detalles), 404 sin_pedido, 422 pedido_cerrado (ya no se puede cancelar) y 429 si pasa de 300 llamadas por minuto con la misma llave. Si una llamada se corta, repítala igual: con el mismo código no se duplica el pedido.
El lote. POST pedidos/lote/ responde un resultado por pedido, en el mismo orden: creado, existente (ese código ya tenía su pedido) o error (con los errores por campo). Uno con error no frena a los demás, y repetir la llamada entera es seguro: lo que ya se creó vuelve como existente. Más de 500 pedidos es un 422 de toda la llamada.
Lo que cambió. GET pedidos/?cambiados_desde=2026-11-08T14:00:00-06:00 trae los pedidos que cambiaron después de ese momento (también fecha=AAAA-MM-DD para los de un día), del que cambió antes al que cambió último, de a 100 (por_pagina hasta 200). Cada pedido trae su estado, prioridad, cliente y dirección con sus códigos, la ruta y su orden, actualizado_en y, si se cerró, el resultado (si se entregó, a qué hora, quién recibió, el motivo y el cobro) y si tiene comprobante. Si la respuesta trae siguiente, pida la página que sigue con cursor=<siguiente>: no se salta ni repite pedidos aunque cambien otros mientras tanto. Para no perder nada, guarde el actualizado_en del último pedido que procesó y úselo como cambiados_desde la próxima vez.
Los avisos. Cada aviso es un POST con JSON a la dirección del webhook, con estas cabeceras: X-Llevu-Evento (pedido.estado, ruta.iniciada, ruta.terminada o prueba), X-Llevu-Entrega (el id de la entrega: un reintento lleva el mismo) y X-Llevu-Firma: t=<segundos>,v1=<firma>. El cuerpo trae id (el del aviso), evento, version, ocurrio_en y el pedido (como en la consulta, más comprobante con la ruta para bajarlo) o la ruta (con sus paradas). Para verificarlo, calcule HMAC-SHA256 con el secreto sobre t, un punto y el cuerpo tal como llegó, y compárelo con v1; descarte los que tengan un t de hace más de 5 minutos. Responda un 2xx apenas lo reciba (en menos de 10 segundos) y procese después: Llevu no sigue redirecciones y puede mandar el mismo aviso más de una vez (use id para no procesarlo dos veces).
import hashlib, hmac, time
def es_de_llevu(secreto: str, cabecera: str, cuerpo: bytes) -> bool: partes = dict(p.split("=", 1) for p in cabecera.split(",")) esperada = hmac.new(secreto.encode(), f"{partes['t']}.".encode() + cuerpo, hashlib.sha256).hexdigest() return hmac.compare_digest(esperada, partes["v1"]) and abs(time.time() - int(partes["t"])) < 300