Automazioni
Cosa riceve il tuo sistema da un'automazione, la richiesta API come utente Workflow e la struttura di un flusso
Ultimo aggiornamento il
Le automazioni fanno eseguire azioni a Tillor stesso, come creare attività o richiamare il tuo sistema. La guida per gli utenti è in Automazioni. Questa pagina descrive ciò che vede un integratore: il messaggio del passaggio Invia webhook, la Richiesta API verso un URL personalizzato o verso l'API di Tillor, e come è costruito un flusso se lo gestisci tramite l'API.
Invia webhook
Il passaggio Invia webhook consegna un messaggio proprio a uno dei webhook dell'organizzazione. È la stessa consegna degli eventi webhook normali, con la stessa firma.
Il messaggio
Il passaggio fa una POST con Content-Type: application/json e questi header:
| Header | Contenuto |
|---|---|
X-Webhook-Event-ID | Un ID univoco per questa consegna. Nei nuovi tentativi il valore resta lo stesso, quindi puoi usarlo per eliminare i duplicati |
X-Webhook-Signature | sha256=<hex>, l'HMAC-SHA256 del body con la signing secret del webhook |
Il body ha la stessa busta del payload del webhook:
{
"event": "automation.invoice_unpaid",
"data": {
"invoice": "2026-0042",
"amountOutstanding": 125.5
},
"timestamp": 1735689600000,
"traceId": "0b0f7c0e-5d3c-4c1e-9b8a-7f1e2a3b4c5d",
"triggeredBy": null
}| Campo | Descrizione |
|---|---|
event | Il Nome dell'evento che hai compilato nel passaggio. È consentito qualsiasi testo, il nome non deve per forza essere nell'elenco delle event-key |
data | Il Payload del passaggio, dopo la sostituzione delle variabili, come JSON |
timestamp | Tempo Unix in millisecondi della consegna |
traceId | Un UUID casuale per ogni consegna. A differenza degli eventi del sistema, non è un trace-id di un'azione di Tillor |
triggeredBy | Sempre null |
Non c'è nessun spanId. Il webhook non deve essere iscritto a una event-key: un messaggio di un'automazione viene sempre consegnato all'endpoint scelto, finché esiste ed è attivo.
Come verificare la firma è spiegato in Verificare la firma.
Tentativi ed esito
Tillor fa fino a 5 tentativi all'interno del passaggio, con 1, 2, 4 e 8 secondi di attesa tra l'uno e l'altro. Un tentativo conta come riuscito con uno stato 2xx. Anche una destinazione bloccata, ad esempio un indirizzo interno, conta come tentativo fallito.
- Se uno dei tentativi riesce, il flusso prosegue dall'uscita Riuscito
- Se tutti e cinque falliscono, il flusso prosegue da Fallito. Il messaggio viene poi ritentato in background secondo lo schema in Comportamento di consegna e retry. Se un tentativo successivo riesce, non risulta più nell'esecuzione, perché questa è già andata avanti
Se il webhook non esiste più o è disattivato, il passaggio non passa da Fallito. L'esecuzione si ferma allora con lo stato Fallito.
Richiesta API verso un URL personalizzato
Con Richiesta API e Invia a su URL personalizzato, un'automazione invia una richiesta HTTP al tuo server.
| Elemento | Comportamento |
|---|---|
| Metodo | GET, POST, PUT, PATCH o DELETE |
| URL | Deve iniziare con https://. Le variabili vengono inserite codificate |
| Body | JSON. Con GET e DELETE non viene inviato. Con un body Tillor imposta Content-Type: application/json |
| Header | Liberi. I valori possono contenere variabili. Host, Content-Length, Transfer-Encoding e Connection vengono ignorati |
| Destinazione | Gli indirizzi privati e interni vengono rifiutati, anche se un normale nome di dominio punta a essi |
| Reindirizzamenti | Non vengono seguiti. Una risposta 3xx conta come fallita |
| Time-out | 15 secondi |
Nessuna firma
A differenza di Invia webhook, Tillor non firma questa richiesta. Se vuoi verificare che una richiesta provenga dalla tua automazione, metti un segreto in un header e controllalo dal tuo lato. Gli header vengono salvati cifrati, quindi una chiave o un token al loro interno è al sicuro.
Una risposta con stato 2xx prosegue da Riuscito, qualsiasi altro stato da Fallito. Se il server non è raggiungibile, o supera il time-out, l'esecuzione si ferma con lo stato Fallito.
Richiesta API verso l'API di Tillor
Con Invia a su Tillor API, l'automazione richiama l'API della propria organizzazione. Indichi solo un percorso, ad esempio /tasks, e Tillor lo mette dopo /api/orgs/{orgId}. Il tuo riferimento API è su OpenAPI.
- Identità: la richiesta viene eseguita come utente Workflow dell'organizzazione. Questo ha tutti i permessi all'interno di quell'organizzazione e in nessun altro posto. Le modifiche risultano quindi a nome di Workflow
- Nessuna chiave necessaria: Tillor esegue la chiamata da sé, senza chiave API e senza passare da internet
- Percorso: deve iniziare con un solo
/.//,..e\non sono consentiti. È ammessa una query string, ad esempio/tasks?terrainId=…&limit=1. Le variabili nel percorso vengono inserite codificate - Bloccato: i percorsi
/automationse/automation-runsvengono rifiutati, anche con una query string, lettere maiuscole, un/finale o una codifica percentuale. Un'automazione non può gestire automazioni - Body: JSON, per
POST,PUTePATCH. ConGETeDELETEnon viene inviato - Menu di scelta: nell'editor c'è Scegli un endpoint API con le operazioni della specifica OpenAPI. Le operazioni interne non ci sono. Se ne scegli una, Tillor compila metodo, percorso e un body di esempio
- Fallimento: una risposta 2xx prosegue da Riuscito. Uno stato di errore, anche un 404 per un percorso che non esiste, passa da Fallito
La risposta nei passaggi successivi
Dopo la richiesta sono disponibili queste variabili:
| Variabile | Contenuto |
|---|---|
{response.status} | Il codice di stato HTTP |
{response.body} | I primi 4000 caratteri della risposta come testo |
{response.data} | La risposta come JSON. null se non è JSON, o se supera i 100.000 caratteri. Gli elenchi si indirizzano con un indice, ad esempio {response.data.items.0.id} |
Una seconda richiesta API nello stesso flusso sostituisce response. Le variabili funzionano come in Variabili e modelli.
La struttura di un flusso
Puoi anche creare e modificare le automazioni tramite l'API. Le operazioni sono in OpenAPI sotto il tag automations. Leggere richiede il permesso automations:read, creare e modificare automations:write (o tutti i permessi). L'utente Workflow stesso non può mai richiamarle. Può essere più comodo costruire un flusso nell'editor e leggerlo come esempio.
Una nuova automazione è sempre disattivata. Attivarla tramite enabled non riesce finché il flusso ha problemi aperti, e un flusso attivo non puoi salvarlo con dei problemi.
La definition è composta da passaggi (nodes), linee (edges) e facoltativamente note (notes). Al massimo 100 passaggi, 200 linee e 50 note.
{
"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) è una spiegazione sulla tela. Non viene mai eseguita e non ha linee. text è al massimo di 2000 caratteri, width e height sono in pixel.
Una linea ha un sourceHandle: l'uscita del passaggio da cui parte.
Passaggio (type) | Uscite (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 | L'id di ogni ramo, più 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 |
Ogni azione tranne webhookSend e httpRequest riceve un'uscita aggiuntiva error non appena continueOnError nel config è su true. Senza questa impostazione un'azione fallita ferma l'esecuzione. Con l'impostazione il flusso prosegue da error, con {error.message} e {error.step} come variabili per i passaggi successivi. L'uscita skipped viene usata quando un passaggio non aveva nulla da fare, e out è allora l'uscita normale.
Con forEach, l'uscita each avvia per ogni elemento un'esecuzione propria per i passaggi successivi, e l'uscita done è il proseguimento normale del flusso stesso.
Un'uscita può portare a più passaggi. L'esecuzione prosegue da sé con la prima linea e per ogni altra linea avvia un'esecuzione propria con gli stessi dati, che corre in parallelo (come l'uscita each di forEach). Le linee che formano un ciclo vengono rifiutate nell'editor. Un flusso ha esattamente un trigger.
Impostazioni per passaggio
I campi sotto config sono i campi dell'editor. I campi di testo accettano variabili tra parentesi graffe.
| Passaggio | Campi in config |
|---|---|
trigger | source (event, webhook, invoiceOverdue, dateReached, daily o cron), webhookVersion (con webhook: testo, aumentalo per sostituire l'indirizzo), eventKey, date (con dateReached: un percorso record.campo verso una colonna DateTime di un modello che un evento invia per intero, come reservation.checkIn o contact.birthday, oppure verso una chiave data dichiarata in una colonna JSON, come document.userMetadata.insuranceExpiryDate; i vecchi nomi come reservationCheckIn continuano a funzionare), dateOffset ({ "amount", "unit" }), dateDirection (before o after), dateTime (exact: l'ora nella data, oppure fixed: il giorno di calendario della data nel fuso orario dell'organizzazione, con dateHour e dateMinute, testo come "9" e "0"; lo spostamento conta sull'orologio, quindi anche attraverso l'ora legale e quella solare), repeatYearly (boolean: contano solo giorno e mese, ogni anno), hour e minute (testo, ad esempio "8" e "0", minuto da 0 a 59), cron (cinque campi; il giorno della settimana può essere 1#2 o 1L, un sesto campo W2:2026-10-05 si ripete ogni due settimane), conditions, oncePerRecord (boolean) |
condition | conditions |
switch | field (un percorso di variabile senza parentesi graffe), cases: un elenco di { "id", "key", "value" } con key come nome del ramo e value come valore a cui il campo deve essere uguale. Al massimo 8 rami |
delay | mode (duration o untilDate), duration e offset come { "amount", "unit" } con unit che è seconds, minutes, hours, days o weeks, dateField (percorso di variabile), direction (before o after) |
taskCreate | title, description, assigneeIds, labelIds, assignActor, priority (LOW, MEDIUM, HIGH o URGENT), viewId, laneId, subtasks (una per riga), dueIn ({ "amount", "unit" }), linkRecords |
taskSetStatus | taskId, status (PENDING, IN_PROGRESS, WAITING o COMPLETED) |
taskFind | title (titolo esatto), linkedToRecords, onlyOpen, onlyTopLevel (boolean) |
documentFind | customerId (modello), documentType (INSURANCE_POLICY, INSURANCE_PAYMENT_PROOF, IDENTITY_CARD, SEPA_MANDATE, OTHER o vuoto), onlyActive, onlyValid (boolean). Risultato sotto foundDocument |
documentUpdate | documentId (modello), documentType (vuoto mantiene il tipo), status (ACTIVE, ARCHIVED o vuoto) |
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 che è 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 (testo, predefinito "0", al massimo 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 (variabili con una data, vuoto significa nessun limite) |
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 o off) |
nfcTagUpdate | customerId, action (block, unblock, topUp o lowBalance), amount (con topUp, in euro per tag) |
deviceCommand | deviceId, command (wake, sleep, reload, identify, testSound o playSound), url (con playSound: il percorso di un suono caricato, come lo compila l'editor, oppure un link https a un file mp3, wav o m4a) |
deviceDisplay | kind (pdf o image), url (link https). Compare su ogni tablet impostato come display, come il PDF di una fattura |
controllerRelay | target (controller o event: il controller dell'evento controller:input:changed che ha avviato il flusso), controllerId (con controller), relay (testo da "1" a "6"), action (pulse, on o off), durationSeconds (con pulse, testo, da 0.001 a 3600 con decimali come "0.5"; il relè si diseccita da solo) |
controllerCommand | target e controllerId come per 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, facoltativamente campi di filtro e ordinamento |
openingHours, waitUntilOpen, waitUntilClosed | Nessun campo |
throttle | window ({ "amount", "unit" } con unit che è minutes, hours, days o weeks), key (vuoto significa per record che ha avviato il flusso) |
recordLookup | model (Customer, Reservation, Occupation, Invoice, Payment, Terrain, Task, Document, AccessMethod, NfcTag, Meter, InboxThread o InboxMessage), recordId. Il risultato è sotto found più il modello, come foundCustomer |
forEach | source (customerAccessMethods, customerNfcTags, customerOpenInvoices, customerUpcomingReservations, customerOccupations, customerDocuments (solo ACTIVE) o terrainMeters), parentId (id del cliente o del terreno). Al massimo 100 elementi, l'elemento è sotto item |
waitUntil | conditions, interval e timeout ({ "amount", "unit" } con unit che è minutes, hours, days o weeks; l'intervallo è di almeno 5 minuti) |
waitForEvent | eventKey, sameRecord (boolean), timeout ({ "amount", "unit" }) |
approval | title, description, assigneeIds, timeout ({ "amount", "unit" }). L'attività è sotto approvalTask.id |
Ogni azione tranne webhookSend e httpRequest | continueOnError (boolean) |
webhookSend | webhookId, event, payload (JSON come testo) |
httpRequest | target (tillor o custom), method, path (con tillor), url (con custom), headers (elenco di { "id", "key", "value" }), body (JSON come testo) |
conditions ha la forma { "match": "all" o "any", "rules": [...] }. Ogni regola è { "id", "field", "operator", "value", "values" }. operator è is, isNot, contains, greaterThan, lessThan, isOneOf, isEmpty o isNotEmpty. values lo usi solo con isOneOf. Un elenco vuoto di regole lascia passare tutto.
Avviare un'automazione con un webhook
Un'automazione con trigger webhook parte con una POST al proprio indirizzo:
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"}'- L'indirizzo completo è nell'editor presso il trigger, oppure tramite
GET /automations/{id}/webhook-url(conautomations:read). Il token è il segreto; non serve un header per l'autenticazione - Il body JSON diventa la variabile
body, la query stringquerye l'istantereceivedAt - Risposta
200con{ "accepted": true }non appena l'esecuzione è partita. Un indirizzo errato, sostituito o disattivato dà404, un body oltre i 100 KB dà400 - Molte chiamate ravvicinate non vengono rifiutate: ogni chiamata valida avvia un'esecuzione. Usa Ignora le ripetizioni nel trigger se la stessa chiamata può arrivare due volte, vedi Ignora le ripetizioni
Un'automazione non può richiamare il proprio webhook tramite una Richiesta API verso l'API di Tillor.
Scegliere una decisione tramite l'API
L'attività di un passaggio Chiedi una decisione ha un'azione per ogni opzione che hai impostato nel passaggio. Recupera l'attività per vedere le actions con il loro id, e scegline una con POST /tasks/{id}/actions/{actionId}/choose. Se l'opzione richiede una nota, inviala insieme come { "note": "..." }. La scelta è definitiva: chiude l'attività e fa proseguire l'automazione dall'uscita di quell'opzione. Possono scegliere i colleghi assegnati (anche tramite un gruppo) e gli amministratori. Vedi OpenAPI per i dettagli.
Per saperne di più
- Webhook per firma, retry ed elenco degli eventi
- API HTTP per autenticazione e contesto dell'organizzazione
- Variabili e modelli per la guida per gli utenti