Automatizaciones
Lo que recibe tu propio sistema de una automatización, la solicitud API como usuario Workflow y la estructura de un flujo
Última actualización el
Las automatizaciones hacen que Tillor ejecute acciones por sí mismo, como crear tareas o llamar a tu sistema. La explicación para usuarios está en Automatizaciones. Esta página describe lo que ve un integrador: el mensaje del paso Enviar webhook, la Solicitud API a una URL propia o a la API de Tillor, y cómo está construido un flujo si lo gestionas mediante la API.
Enviar webhook
El paso Enviar webhook entrega un mensaje propio a uno de los webhooks de la organización. Es la misma entrega que con los eventos de webhook normales, con la misma firma.
El mensaje
El paso hace un POST con Content-Type: application/json y estas cabeceras:
| Cabecera | Contenido |
|---|---|
X-Webhook-Event-ID | Un ID único para esta entrega. En los reintentos el valor se mantiene igual, así que puedes deduplicar con él |
X-Webhook-Signature | sha256=<hex>, el HMAC-SHA256 del cuerpo con el signing secret del webhook |
El cuerpo tiene el mismo sobre que la carga útil del webhook:
{
"event": "automation.invoice_unpaid",
"data": {
"invoice": "2026-0042",
"amountOutstanding": 125.5
},
"timestamp": 1735689600000,
"traceId": "0b0f7c0e-5d3c-4c1e-9b8a-7f1e2a3b4c5d",
"triggeredBy": null
}| Campo | Descripción |
|---|---|
event | El Nombre del evento que rellenaste en el paso. Se permite cualquier texto, el nombre no tiene que estar en la lista de claves de evento |
data | La Carga útil del paso, una vez rellenadas las variables, como JSON |
timestamp | Hora Unix en milisegundos de la entrega |
traceId | Un UUID aleatorio por entrega. A diferencia de los eventos del sistema, no es el ID de traza de una acción de Tillor |
triggeredBy | Siempre null |
No hay spanId. El webhook no tiene que estar suscrito a una clave de evento: un mensaje de una automatización siempre se entrega en el endpoint elegido, mientras exista y esté activado.
Cómo verificar la firma está en Verificar la firma.
Intentos y resultado
Dentro del paso, Tillor hace hasta 5 intentos, con 1, 2, 4 y 8 segundos de espera entre ellos. Un intento cuenta como correcto con un estado 2xx. También un destino bloqueado, por ejemplo una dirección interna, cuenta como intento fallido.
- Si uno de los intentos tiene éxito, el flujo continúa por la salida Correcto
- Si los cinco fallan, el flujo continúa por Fallido. Después, el mensaje se sigue reintentando en segundo plano según el calendario de Comportamiento de entrega y reintentos. Si uno de esos intentos posteriores tiene éxito, ya no se refleja en la ejecución, porque esta ya ha continuado
Si el webhook ya no existe o está desactivado, el paso no sale por Fallido. La ejecución se detiene entonces con el estado Fallido.
Solicitud API a una URL propia
Con Solicitud API y Enviar a en URL personalizada, una automatización envía una solicitud HTTP a tu servidor.
| Parte | Comportamiento |
|---|---|
| Método | GET, POST, PUT, PATCH o DELETE |
| URL | Debe empezar por https://. Las variables se rellenan codificadas |
| Cuerpo | JSON. No se envía con GET y DELETE. Con cuerpo, Tillor pone Content-Type: application/json |
| Cabeceras | Libres. Los valores pueden contener variables. Host, Content-Length, Transfer-Encoding y Connection se ignoran |
| Destino | Las direcciones privadas e internas se rechazan, incluso si un nombre de dominio normal apunta a ellas |
| Redirecciones | No se siguen. Una respuesta 3xx cuenta como fallida |
| Tiempo de espera | 15 segundos |
Sin firma
A diferencia de Enviar webhook, Tillor no firma esta solicitud. Si quieres comprobar que una solicitud procede de tu automatización, pon un secreto en una cabecera y compruébalo por tu lado. Las cabeceras se guardan cifradas, así que una clave o un token ahí dentro está seguro.
Una respuesta con estado 2xx continúa por Correcto, cualquier otro estado por Fallido. Si no se puede alcanzar el servidor, o supera el tiempo de espera, la ejecución se detiene con el estado Fallido.
Solicitud API a la API de Tillor
Con Enviar a en Tillor API, la automatización llama a la API de la propia organización. Solo indicas una ruta, por ejemplo /tasks, y Tillor la coloca detrás de /api/orgs/{orgId}. Tu referencia de API está en OpenAPI.
- Identidad: la solicitud se ejecuta como el usuario Workflow de la organización. Tiene todos los permisos dentro de esa organización y en ningún otro sitio. Los cambios figuran, pues, a nombre de Workflow
- No hace falta clave: Tillor ejecuta la llamada por sí mismo, sin clave de API y sin pasar por internet
- Ruta: debe empezar por una sola
/. No se permiten//,..ni\. Se admite una cadena de consulta, por ejemplo/tasks?terrainId=…&limit=1. Las variables en la ruta se rellenan codificadas - Bloqueado: las rutas
/automationsy/automation-runsse rechazan, también con cadena de consulta, mayúsculas, una/final o codificación porcentual. Una automatización no puede gestionar automatizaciones - Cuerpo: JSON, para
POST,PUTyPATCH. ConGETyDELETEno se envía - Menú de selección: en el editor figura Elige un endpoint de la API con las operaciones de la especificación OpenAPI. Las operaciones internas no aparecen. Si eliges una, Tillor rellena el método, la ruta y un cuerpo de ejemplo
- Fallos: una respuesta 2xx continúa por Correcto. Un estado de error, también un 404 para una ruta que no existe, sale por Fallido
La respuesta en pasos posteriores
Tras la solicitud están disponibles estas variables:
| Variable | Contenido |
|---|---|
{response.status} | El código de estado HTTP |
{response.body} | Los primeros 4000 caracteres de la respuesta como texto |
{response.data} | La respuesta como JSON. null si no es JSON, o si tiene más de 100.000 caracteres. Las listas se direccionan con un índice, por ejemplo {response.data.items.0.id} |
Una segunda solicitud API en el mismo flujo sustituye a response. Las variables funcionan como en Variables y plantillas.
La estructura de un flujo
También puedes crear y modificar automatizaciones mediante la API. Las operaciones están en OpenAPI bajo la etiqueta automations. Leer requiere el permiso automations:read, crear y modificar automations:write (o todos los permisos). El propio usuario Workflow nunca puede llamarlas. Puede ser más práctico construir un flujo en el editor y leerlo como ejemplo.
Una automatización nueva siempre está desactivada. Activarla mediante enabled no funciona mientras el flujo tenga problemas abiertos, y un flujo activado no se puede guardar con problemas.
La definition consta de pasos (nodes), líneas (edges) y, opcionalmente, notas (notes). Máximo 100 pasos, 200 líneas y 50 notas.
{
"nodes": [
{
"id": "trigger",
"type": "trigger",
"label": "",
"position": { "x": 0, "y": 0 },
"config": { "source": "event", "eventKey": "occupation:ended" }
},
{
"id": "task",
"type": "taskCreate",
"label": "",
"position": { "x": 0, "y": 190 },
"config": { "title": "Controleer {occupation.terrain.name}", "priority": "HIGH" }
}
],
"edges": [
{ "id": "trigger:out:task", "source": "trigger", "target": "task", "sourceHandle": "out" }
],
"notes": [
{
"id": "note_1",
"text": "Maakt na elk vertrek een controletaak.",
"position": { "x": 340, "y": 0 },
"width": 240,
"height": 140
}
]
}Una nota (notes) es una explicación en el lienzo. Nunca se ejecuta y no tiene líneas. text tiene como máximo 2000 caracteres, width y height están en píxeles.
Una línea tiene un sourceHandle: la salida del paso de la que parte.
Paso (type) | Salidas (sourceHandle) |
|---|---|
trigger, delay, waitUntilOpen, waitUntilClosed, taskCreate, taskSetStatus, taskSetPriority, taskUpdate, invoiceReminderSend, invoiceSend, invoicePrint, occupationExtendInvoice, staffNotify, commentCreate, inboxReply, inboxTag, customerUpdate, accessMethodCreate, accessMethodUpdate, accessMethodSendQr, accessPointControl, meterSwitch, nfcTagUpdate, deviceCommand, deviceDisplay, deviceSlideshow, controllerRelay, controllerCommand | out |
customerMessageSend, reservationSetStatus, invoiceBook, paymentLinkCreate, mandateRequest, mandateCollect, consumptionInvoice | out, skipped |
condition | yes, no |
switch | El id de cada rama, más default |
openingHours | open, closed |
throttle | pass, blocked |
taskFind, documentFind, recordLookup | found, notFound |
forEach | each, done |
waitUntil | met, timeout |
waitForEvent | received, timeout |
approval | approved, rejected, timeout |
webhookSend, httpRequest | success, failed |
Cada acción, salvo webhookSend y httpRequest, recibe una salida adicional error en cuanto continueOnError está en true en el config. Sin ese ajuste, una acción fallida detiene la ejecución. Con él, el flujo continúa por error, con {error.message} y {error.step} como variables para los pasos siguientes. La salida skipped se usa cuando un paso no tenía nada que hacer, y out es entonces la salida normal.
En forEach, la salida each inicia por cada elemento una ejecución propia para los pasos siguientes, y la salida done es el camino normal de continuación del propio flujo.
Una salida puede llevar a varios pasos. La ejecución sigue por sí misma con la primera línea e inicia, por cada otra línea, una ejecución propia con los mismos datos, que corre en paralelo (como la salida each de forEach). Las líneas que forman un bucle se rechazan en el editor. Un flujo tiene exactamente un trigger.
Ajustes por paso
Los campos de config son los campos del editor. Los campos de texto aceptan variables entre llaves.
| Paso | Campos en config |
|---|---|
trigger | source (event, webhook, invoiceOverdue, dateReached, daily o cron), webhookVersion (con webhook: texto, auméntalo para sustituir la dirección), eventKey, date (con dateReached: una ruta record.campo a una columna DateTime de un modelo que envía un evento completo, como reservation.checkIn o contact.birthday, o a una clave de fecha declarada en una columna JSON, como document.userMetadata.insuranceExpiryDate; los nombres antiguos como reservationCheckIn siguen funcionando), dateOffset ({ "amount", "unit" }), dateDirection (before o after), dateTime (exact: la hora de la fecha, o fixed: el día natural de la fecha en la zona horaria de la organización, con dateHour y dateMinute, texto como "9" y "0"; el desplazamiento cuenta en el reloj, es decir, atravesando el horario de verano e invierno), repeatYearly (booleano: solo cuentan día y mes, cada año), hour y minute (texto, por ejemplo "8" y "0", minuto de 0 a 59), cron (cinco campos; el día de la semana puede ser 1#2 o 1L, un sexto campo W2:2026-10-05 repite cada dos semanas), conditions, oncePerRecord (booleano) |
condition | conditions |
switch | field (una ruta de variable sin llaves), cases: una lista de { "id", "key", "value" } con key como nombre de la rama y value como valor al que debe ser igual el campo. Máximo 8 ramas |
delay | mode (duration o untilDate), duration y offset como { "amount", "unit" } con unit igual a seconds, minutes, hours, days o weeks, dateField (ruta de variable), direction (before o after) |
taskCreate | title, description, assigneeIds, labelIds, assignActor, priority (LOW, MEDIUM, HIGH o URGENT), viewId, laneId, subtasks (una por línea), dueIn ({ "amount", "unit" }), linkRecords |
taskSetStatus | taskId, status (PENDING, IN_PROGRESS, WAITING o COMPLETED) |
taskFind | title (título exacto), linkedToRecords, onlyOpen, onlyTopLevel (booleanos) |
documentFind | customerId (plantilla), documentType (INSURANCE_POLICY, INSURANCE_PAYMENT_PROOF, IDENTITY_CARD, SEPA_MANDATE, OTHER o vacío), onlyActive, onlyValid (booleanos). Resultado bajo foundDocument |
documentUpdate | documentId (plantilla), documentType (vacío mantiene el tipo), status (ACTIVE, ARCHIVED o vacío) |
taskSetPriority | taskId, mode (raise, lower o set), priority (con set), maxPriority (con raise) |
taskUpdate | taskId, title, assigneeIds, assignActor, assigneeMode (add o replace), labelIds, dueIn ({ "amount", "unit" } con unit igual a minutes, hours, days o weeks) |
invoiceReminderSend, invoiceSend, invoiceBook | invoiceId |
invoicePrint | invoiceId, printer (a4, pos o tablet) |
paymentLinkCreate | invoiceId, method (any, BANCONTACT, IDEAL, CREDITCARD o PAYCONIQ) |
mandateRequest | customerId, mode (request o reminder) |
mandateCollect | invoiceId, delayDays (texto, predeterminado "0", como máximo 7) |
consumptionInvoice | customerId, sendEmail, sendSms |
occupationExtendInvoice | occupationId, includeConsumption |
customerMessageSend | customerId, subject, body, sendEmail, sendSms, sendWhatsApp |
staffNotify | userIds, notifyActor, title, body, sendPush, sendEmail |
commentCreate | content, target (event o task), taskId (con task) |
inboxReply | threadId, body, keepAssistant |
inboxTag | threadId, tagIds, archive |
customerUpdate | customerId, status (keep, DEFAULT, PREFERRED, VIP, WARNING o BLACKLISTED), notes |
reservationSetStatus | reservationId, action (checkIn, checkOut o cancel) |
accessMethodCreate | customerId, type (QR_CODE, LPR, PIN_CODE o NFC), value, periodFrom, periodTo (variables con una fecha, vacío significa sin límite) |
accessMethodUpdate | scope (one o customer), accessMethodId (con one), customerId (con customer), status (keep, ACTIVE, SOFT_BLOCKED, BLOCKED o INACTIVE), periodTo |
accessMethodSendQr | accessMethodId, sendEmail, sendSms, sendWhatsApp, print (none, label o thermal) |
accessPointControl | accessPointId, action (open o close) |
meterSwitch | target (terrain o meter), terrainId (con terrain), meterId (con meter), state (on u off) |
nfcTagUpdate | customerId, action (block, unblock, topUp o lowBalance), amount (con topUp, en euros por tag) |
deviceCommand | deviceId, command (wake, sleep, reload, identify, testSound o playSound), url (con playSound: la ruta de un sonido subido, tal como la rellena el editor, o un enlace https a un archivo mp3, wav o m4a) |
deviceDisplay | kind (pdf o image), url (enlace https). Aparece en cada tableta configurada como pantalla, como un PDF de factura |
controllerRelay | target (controller o event: el controlador del evento controller:input:changed que inició el flujo), controllerId (con controller), relay (texto "1" a "6"), action (pulse, on u off), durationSeconds (con pulse, texto, de 0.001 a 3600 con decimales como "0.5"; el relé vuelve a soltarse solo) |
controllerCommand | target y controllerId como en controllerRelay, command (reboot, rebootExtension, otaUpdate, forceOtaUpdate, extensionOtaUpdate o refreshSettings) |
deviceSlideshow | scope (displays o device), mode (screensaver o background), source (feed o urls). Con feed: itemsPath, imageField, opcionalmente campos de filtro y ordenación |
openingHours, waitUntilOpen, waitUntilClosed | Sin campos |
throttle | window ({ "amount", "unit" } con unit igual a minutes, hours, days o weeks), key (vacío es por registro que inició el flujo) |
recordLookup | model (Customer, Reservation, Occupation, Invoice, Payment, Terrain, Task, Document, AccessMethod, NfcTag, Meter, InboxThread o InboxMessage), recordId. El resultado está bajo found más el modelo, como foundCustomer |
forEach | source (customerAccessMethods, customerNfcTags, customerOpenInvoices, customerUpcomingReservations, customerOccupations, customerDocuments (solo ACTIVE) o terrainMeters), parentId (id del cliente o del terreno). Como máximo 100 elementos, el elemento está bajo item |
waitUntil | conditions, interval y timeout ({ "amount", "unit" } con unit igual a minutes, hours, days o weeks; el intervalo es de al menos 5 minutos) |
waitForEvent | eventKey, sameRecord (booleano), timeout ({ "amount", "unit" }) |
approval | title, description, assigneeIds, timeout ({ "amount", "unit" }). La tarea está bajo approvalTask.id |
Cada acción salvo webhookSend y httpRequest | continueOnError (booleano) |
webhookSend | webhookId, event, payload (JSON como texto) |
httpRequest | target (tillor o custom), method, path (con tillor), url (con custom), headers (lista de { "id", "key", "value" }), body (JSON como texto) |
conditions tiene la forma { "match": "all" o "any", "rules": [...] }. Cada regla es { "id", "field", "operator", "value", "values" }. operator es is, isNot, contains, greaterThan, lessThan, isOneOf, isEmpty o isNotEmpty. values solo se usa con isOneOf. Una lista de reglas vacía deja pasar todo.
Iniciar una automatización con un webhook
Una automatización con trigger webhook se inicia con un POST a su propia dirección:
curl -X POST "https://tillor.eu/api/orgs/org_abc123/automation-hooks/aut_xxx/<token>?bron=website" \
-H "Content-Type: application/json" \
-d '{"email":"jan@voorbeeld.nl","aankomst":"2026-07-14"}'- La dirección completa está en el editor, en el trigger, o mediante
GET /automations/{id}/webhook-url(conautomations:read). El token es el secreto; no hace falta una cabecera para autenticar - El cuerpo JSON pasa a ser la variable
body, la cadena de consultaqueryy el momentoreceivedAt - Respuesta
200con{ "accepted": true }en cuanto se inicia la ejecución. Una dirección incorrecta, sustituida o desactivada da404, un cuerpo de más de 100 KB da400 - Muchas llamadas seguidas en poco tiempo no se rechazan: cada llamada válida inicia una ejecución. Usa Ignorar repeticiones en el trigger si la misma llamada puede llegar dos veces, consulta Ignorar repeticiones
Una automatización no puede llamar a su propio webhook mediante una Solicitud API a la API de Tillor.
Elegir una decisión mediante la API
La tarea de un paso Pedir una decisión tiene una acción por cada opción que configuraste en el paso. Obtén la tarea para ver las actions con su id, y elige una con POST /tasks/{id}/actions/{actionId}/choose. Si la opción pide una nota, envíala como { "note": "..." }. La elección es definitiva: cierra la tarea y continúa la automatización por la salida de esa opción. Pueden elegir los compañeros asignados (también mediante un grupo) y los administradores. Consulta OpenAPI para los detalles.
Leer más
- Webhooks para la firma, los reintentos y la lista de eventos
- API HTTP para la autenticación y el contexto de la organización
- Variables y plantillas para la explicación para usuarios