n8n: HTTP Request para llamar y Code para verificar
Tampoco hay nodo oficial, y aquí importa menos que en ningún sitio: n8n tiene un nodo de código, y eso es lo que hace falta para comprobar la firma de un webhook antes de fiarse de él.
La API está en marcha. Esto es lo que hay hoy y lo que no
Funciona. Las rutas de esta documentación están desplegadas y respondiendo en https://erp.cairos.es/api/v1/. Los ejemplos de estas páginas se pueden copiar y ejecutar. La especificación completa está en openapi.json, que es lo que importan Make y n8n.
Las claves te las creas tú, desde Desarrolladores en tu cuenta de Cairos. Empieza con una cai_test_: trabaja contra tus datos de verdad pero no registra en VeriFactu ni envía correos, así que puedes montar tu integración sin ensuciar una serie de facturación.
Y lo que todavía NO hay, dicho sin adornos: ningún conector oficial. Ni app de Shopify, ni plugin de WooCommerce, ni módulo publicado en Make o n8n. Con la API y su especificación se pueden construir —para eso están las guías de esta sección— pero construirlos es trabajo, y ese trabajo no está hecho.
Si montas algo con esto, escríbenos a info@cairos.es. Nos interesa especialmente lo que te falte del contrato: es lo que decide qué se amplía primero.
No hay nodo oficial de Cairos en n8n
No está publicado y no hay fecha. Lo que hay es la API —con su lista de eventos, su OpenAPI y sus webhooks— y esta página, que te dice exactamente qué enganchar y con qué código. Dentro de Cairos hay además recetas ya montadas y un flujo de n8n descargable, en API e integraciones → Empezar.
Si tu tienda es de PrestaShop, éste es tu camino
Cairos conecta solo con Shopify, WooCommerce y Odoo. Con PrestaShop todavía no, y está a medias a propósito: el tipo de IVA de cada línea no viene en la línea —hay que resolverlo contra otros dos recursos de su API—, los cupones son un recurso aparte que se aplica al pedido y no a las líneas, y cada tienda llama de una manera distinta al estado que significa «vendido», porque esos estados los edita el comerciante. Facturar tres de cada cuatro pedidos e inventarse el IVA del cuarto es peor que decir que no: el «no» se ve el primer día y el IVA mal puesto se ve en el 303.
Mientras tanto, sus pedidos entran perfectamente por aquí: PrestaShop sabe avisar a una dirección cuando entra un pedido, y lo que hay debajo es exactamente el mismo recorrido que cuenta esta página. Contado para quien vende y no para quien programa, está en Cairos y PrestaShop.
La credencial, que se monta una vez
n8n no tiene nodo de Cairos, y no hace falta: el nodo HTTP Request llama a cualquier API. Lo que sí conviene montar bien desde el principio es la credencial, para no repetir la clave en cada nodo ni dejarla escrita en el flujo.
Se crea una credencial de tipo Header Auth con estos dos valores:
Name: Authorization
Value: Bearer cai_live_TU_CLAVEA partir de ahí, cada nodo HTTP Request usa esa credencial y no vuelve a saber nada de la clave. Cuando la rotes, se cambia en un sitio.
Llamar a Cairos
Un nodo HTTP Request por operación. Lo que hay que rellenar es siempre lo mismo:
| Campo del nodo | Valor |
|---|---|
| Method | POST |
| URL | https://erp.cairos.es |
| Authentication | Genérica, con la credencial Header Auth de arriba |
| Send Body | Activado, en JSON |
| Headers | Idempotency-Key, con el identificador del pedido del nodo anterior |
Y activa el reintento del propio nodo sólo si has puesto la llave de idempotencia. Un reintento de un POST sin llave es la forma más rápida de acabar con dos facturas.
El cuerpo, en modo JSON, con las expresiones de n8n para tomar los datos del nodo anterior:
{
"contacto_id": "{{ $json.id }}",
"serie": "WEB",
"fecha_emision": "{{ $now.format('yyyy-MM-dd') }}",
"lineas": [
{
"descripcion": "{{ $('Pedido').item.json.titulo }}",
"cantidad": 1,
"precio": 180.00,
"tipo_iva": 21
}
]
}Los nombres van tal cual: fecha_emision, descripcion y tipo_iva. Un campo que no existe no se ignora, devuelve 422 y lo nombra en error.detalles.campos, así que ése es el primer sitio donde mirar cuando el nodo se pone rojo.
Recibir sucesos de Cairos, que es donde n8n brilla
Aquí es donde n8n gana a las alternativas sin código: tiene un nodo de código donde se puede comprobar la firma de un webhook, que es lo único que no es negociable al abrir un extremo público.
El montaje son tres nodos:
| # | Nodo | Qué hace |
|---|---|---|
| 1 | Webhook | Recibe el POST de Cairos. Hay que activar la opción de cuerpo sin procesar: sin ella no se puede comprobar la firma. |
| 2 | Code | Comprueba la firma con el secreto de la suscripción y corta si no cuadra. |
| 3 | Lo que quieras | Avisar por Slack, escribir en una hoja, actualizar el pedido en tu tienda. |
const crypto = require("crypto");
const secreto = $env.CAIROS_SECRETO;
const entrada = $input.first().json;
// El cuerpo CRUDO, tal y como llegó. Si aquí usas el JSON ya
// convertido, la firma no cuadrará nunca y perderás una tarde.
const crudo = entrada.rawBody || entrada.body;
// La cabecera se llama "x-cairos-firma", en minuscula.
const cabecera = entrada.headers["x-cairos-firma"] || "";
const partes = Object.fromEntries(
cabecera.split(",").map((t) => t.trim().split("="))
);
const ahora = Math.floor(Date.now() / 1000);
if (!partes.t || Math.abs(ahora - Number(partes.t)) > 300) {
throw new Error("Suceso caducado o sin marca de tiempo");
}
const esperada = crypto
.createHmac("sha256", secreto)
.update(partes.t + "." + crudo)
.digest("hex");
if (esperada !== partes.v1) {
throw new Error("Firma no valida");
}
return [{ json: JSON.parse(crudo) }];Cómo se calcula esa firma y por qué la marca de tiempo va dentro está en webhooks. Y la dirección que te da el nodo Webhook es la que se registra con POST /webhooks.
Recorrer listas largas
Para leer todas las facturas de un año no vale una sola llamada: la API pagina con cursor. Y aquí n8n lo pone fácil, porque siguiente ya es la URL entera de la página que sigue, con tus filtros dentro: no hay que recomponer ninguna consulta.
- Pide con
?limite=200&orden=antiguo, que es el orden que no se mueve mientras recorres. - Coge
siguientede la respuesta y úsalo como URL tal cual. No es el id del último elemento ni un cursor que haya que pegar a mano en?desde=: es la dirección completa. - Para cuando
siguientevenga anull, no cuandodatosvenga vacío.
En el paginado del propio nodo HTTP Request eso se configura con el modo de «siguiente URL» y la expresión {{ $response.body.siguiente }}, y la condición de parada es que ese campo sea nulo.
El detalle de la paginación, con el bucle escrito en JavaScript y en PHP, está en errores y paginación.
Preguntas sobre n8n
Authorization y valor Bearer más la clave. Los nodos la referencian y no la contienen, así que al rotarla se cambia en un sitio.Idempotency-Key. Un reintento de un POST sin llave es exactamente cómo se acaba con dos facturas del mismo pedido.Monta el flujo y rellena la credencial
Las rutas, las cabeceras y la firma son las que usa el servidor hoy. La clave te la creas tú desde tu cuenta, y con una de pruebas puedes lanzar el flujo sin ensuciar nada.
HTTP Request · Header Auth · Cuerpo sin procesar en el Webhook