API y webhooks

Si llevas tu contabilidad o tu CRM en otra herramienta y quieres que Recua se hable con esa otra herramienta, lo haces con la API pública REST y los webhooks salientes. Estan en Sistema → API e integraciones.

Cuando usar la API

  • Pull: tu ERP necesita listar portes → usa la API REST con una API key.
  • Push: cuando ocurre algo en Recua (porte asignado, firmado, entregado...) quieres notificar a un sistema externo → usa webhooks.

Si solo quieres ver portes en otra herramienta sin sincronia real, exporta CSV desde Analitica — es más simple.

Crear una API key

En API e integraciones → "Nueva API key":

  • Nombre: descripcion para que sepas que sistema la usa ("ERP Sage 50", "Power BI dashboard").
  • Permisos (scope): que puede hacer esta key. Las dos opciones disponibles son:
    • portes:read — leer portes.
    • portes:write — crear portes desde el ERP.
    • (más scopes vendran con tiempo; pidenos si necesitas algo concreto, como clientes o facturas).

Al crear la key, Recua te muestra el token completo UNA SOLA VEZ — algo como ta_a1b2c3d4e5f6_<44 chars>. Copialo y guardalo en tu gestor de secretos. Solo se guarda el hash en la base de datos: si lo pierdes, no hay forma de recuperarlo y toca generar una key nueva.

Usar la API

Pasa la key en el header Authorization: Bearer <token>:

curl -H "Authorization: Bearer ta_..." \
  https://recua.app/api/v1/portes?desde=2026-05-01

Endpoints actuales:

  • GET /api/v1/portes — listar portes de tu empresa (más recientes primero). Filtros por query string: estado, desde y hasta (sobre la fecha de carga), limit (por defecto 50, hasta 200).
  • GET /api/v1/portes/{id} — detalle de un porte: datos del conductor, cargador, destinatario y vehiculo, el historial de eventos del porte y las reservas (observaciones del transportista sobre el estado de la mercancia, art. 8 Convenio CMR) firmadas por el expedidor o el destinatario.
  • POST /api/v1/portes — crear porte (requiere scope portes:write). Solo conductor_id es obligatorio; cargador_id, destinatario_id y vehiculo_id son opcionales pero, si los mandas, tienen que pertenecer a tu empresa.

Respuestas en JSON estándar. Errores con código HTTP:

  • 401 falta el header, el token tiene formato invalido, o esta revocado.
  • 402 tu empresa no puede operar ahora mismo (trial caducado o suscripción impagada/cancelada). Solo bloquea crear portes nuevos por API; la lectura sigue funcionando.
  • 403 token sin el scope requerido.
  • 404 recurso no existe o no pertenece a tu empresa.

Revocar una API key

Si sospechas que la key se ha filtrado o el sistema que la usaba ya no opera, en la lista de keys pulsa "Revocar". La key queda invalida inmediatamente y los siguientes requests del sistema externo daran 401. No se puede deshacer — genera una key nueva si la necesitas.

Las keys revocadas siguen apareciendo en la lista, marcadas con la etiqueta "Revocada", para que quede el rastro de que claves ha tenido la empresa. No se pueden reactivar.

Webhooks salientes

Si quieres que Recua te avise cuando pasa algo, en lugar de preguntar tu cada minuto:

"Nuevo webhook":

  • URL: el endpoint en tu sistema (http o https). Tiene que ser publica — Recua resuelve el DNS antes de llamar y bloquea localhost, IPs privadas (192.168.x, 10.x.x.x...) y metadatos de nube (169.254.169.254), asi que no puedes apuntar a un servicio interno.
  • Eventos: a que cambios de estado del porte suscribirte. Disponibles: porte.creado, porte.asignado, porte.cargando, porte.en_ruta, porte.descargando, porte.firmado, porte.entregado, porte.incidencia, porte.cancelado.
  • Secret: Recua firma cada peticion con HMAC SHA256 usando este secret. Verifica la firma en tu endpoint para asegurar que la peticion viene de Recua y no de un tercero.

No hay edicion de un webhook ya creado: para cambiar la URL o los eventos, borralo y crea uno nuevo. Puedes pausarlo ("Pausar"/"Reactivar" en la lista) sin borrarlo si quieres detener los envios temporalmente.

porte.firmado no es un evento único

Un porte puede disparar porte.firmado más de una vez a lo largo de su ciclo de vida (firma del conductor en la recogida, firma del cargador, firma del conductor en la entrega). El body incluye campo para que sepas cual de las firmas fue.

Que trae el body

{
  "evento": "porte.entregado",
  "enviado_at": "2026-08-03T10:00:00.000Z",
  "data": { "porte_id": "...", "campo": "firma_destinatario" }
}

El contenido de data varia segun el evento y desde donde se disparo: si viene de POST /api/v1/portes incluye el porte completo; si viene de una firma o de un cambio de estado incluye como mínimo porte_id.

Como verificar la firma del webhook

Recua envia un header X-Recua-Signature con formato:

t=<timestamp>,v1=<hmac>

Tu endpoint:

  1. Lee timestamp y hmac del header.
  2. Calcula hmac_esperado = HMAC-SHA256(secret, timestamp + "." + body).
  3. Si hmac_esperado != hmac, rechaza la peticion (probablemente no es de Recua).
  4. Si la diferencia entre timestamp y ahora supera 5 minutos, rechaza (proteccion contra replay).

Ejemplo en Node.js:

import { createHmac } from "node:crypto";

const [tsPart, sigPart] = req.headers["x-recua-signature"].split(",");
const ts = tsPart.split("=")[1];
const recibido = sigPart.split("=")[1];

const calculado = createHmac("sha256", SECRET)
  .update(`${ts}.${rawBody}`)
  .digest("hex");

if (calculado !== recibido) return res.status(401).end();
if (Date.now() / 1000 - parseInt(ts) > 300) return res.status(401).end();

Sin reintentos automáticos

Recua intenta la entrega una sola vez, con un timeout de 5 segundos. Si tu endpoint no responde o devuelve un error, Recua no vuelve a intentarlo — solo actualiza el estado y la fecha del último envio en la lista de webhooks. Tu endpoint tiene que responder rapido y con un 2xx; si necesitas procesar el evento de forma lenta, encolalo internamente y contesta enseguida.

Errores comunes

  • La fecha de "último envio" aparece sin ningun código al lado: Recua no llego a recibir respuesta del endpoint — la URL estaba bloqueada por la proteccion SSRF, el DNS no resolvio, o tu servidor no contesto a tiempo (timeout de 5 s) o rechazo la conexion. Revisa que la URL sea publica y que tu endpoint conteste rapido.
  • El código aparece en rojo: tu endpoint respondio pero con un error (4xx/5xx). Como no hay reintentos, ese evento en concreto no se reenvia — corrige tu endpoint para el próximo evento, o recupera el estado actual con GET /api/v1/portes/{id}.
  • Falta un evento: al no haber reintentos, un fallo puntual (caida, deploy, timeout) hace que ese evento se pierda sin más rastro que el estado de "último envio". Si tu integracion no puede permitirse perder eventos, usa el webhook como disparador para ir a buscar el estado real por API en vez de fiarte solo del payload que llega.

¿Necesitas un scope, un evento o un endpoint que no existe (clientes, facturas, actualizar un porte via API)? Cuentanoslo en hola@recua.app, va a la cola.