Skip to Content

Automations

What your own system receives from an automation, the API request as the Workflow user and the structure of a flow

Last updated on

Automations let Tillor carry out actions itself, such as creating tasks or calling your system. The user guide is at Automations. This page describes what an integrator sees: the message from the Send webhook step, the API request to your own URL or to the Tillor API, and how a flow is built if you manage it through the API.

Send webhook

The Send webhook step delivers a custom message to one of the organisation's webhooks. It is the same delivery as for ordinary webhook events, with the same signature.

The message

The step does a POST with Content-Type: application/json and these headers:

HeaderContent
X-Webhook-Event-IDA unique ID for this delivery. The value stays the same on new attempts, so you can deduplicate on it
X-Webhook-Signaturesha256=<hex>, the HMAC-SHA256 of the body with the webhook's signing secret

The body has the same envelope as the webhook payload:

{
  "event": "automation.invoice_unpaid",
  "data": {
    "invoice": "2026-0042",
    "amountOutstanding": 125.5
  },
  "timestamp": 1735689600000,
  "traceId": "0b0f7c0e-5d3c-4c1e-9b8a-7f1e2a3b4c5d",
  "triggeredBy": null
}
FieldDescription
eventThe Event name you filled in in the step. Any text is allowed, the name does not have to be in the list of event keys
dataThe Payload from the step, after the variables have been filled in, as JSON
timestampUnix time in milliseconds of the delivery
traceIdA random UUID per delivery. Unlike events from the system, this is not a trace ID of a Tillor action
triggeredByAlways null

There is no spanId. The webhook does not have to be subscribed to an event key: a message from an automation is always delivered to the chosen endpoint, as long as it exists and is enabled.

How to verify the signature is described under Verifying the signature.

Attempts and outcome

Tillor makes up to 5 attempts within the step, waiting 1, 2, 4 and 8 seconds between them. An attempt counts as successful on a 2xx status. A target that is blocked, for example an internal address, also counts as a failed attempt.

  • If one of the attempts succeeds, the flow continues through the Succeeded exit
  • If all five fail, the flow continues through Failed. The message is then retried in the background according to the schedule under Delivery behaviour and retries. If such a later attempt succeeds, it no longer shows up in the run, because the run has already moved on

If the webhook no longer exists or is disabled, the step does not go through Failed. The run then stops with the status Failed.

API request to your own URL

With API request and Send to set to Custom URL, an automation sends an HTTP request to your server.

PartBehaviour
MethodGET, POST, PUT, PATCH or DELETE
URLMust start with https://. Variables are filled in encoded
BodyJSON. Not sent for GET and DELETE. With a body, Tillor sets Content-Type: application/json
HeadersFree to choose. The values may contain variables. Host, Content-Length, Transfer-Encoding and Connection are ignored
TargetPrivate and internal addresses are refused, even if an ordinary domain name points to them
RedirectsAre not followed. A 3xx response counts as failed
Timeout15 seconds

No signature

Unlike Send webhook, Tillor does not sign this request. If you want to check that a request comes from your automation, put a secret in a header and check it on your side. Headers are stored encrypted, so a key or token in them is safe.

A response with a 2xx status continues through Succeeded, any other status through Failed. If the server cannot be reached, or exceeds the timeout, the run stops with the status Failed.

API request to the Tillor API

With Send to set to Tillor API, the automation calls the API of its own organisation. You only give a path, for example /tasks, and Tillor places it after /api/orgs/{orgId}. Your API reference is at OpenAPI.

  • Identity: the request is executed as the organisation's Workflow user. That user has all permissions within that organisation and nowhere else. Changes are therefore recorded under the name Workflow
  • No key needed: Tillor makes the call itself, without an API key and without going over the internet
  • Path: must start with a single /. //, .. and \ are not allowed. A query string is allowed, for example /tasks?terrainId=…&limit=1. Variables in the path are filled in encoded
  • Blocked: the paths /automations and /automation-runs are refused, also with a query string, capital letters, a trailing / or percent-encoding. An automation cannot manage automations
  • Body: JSON, for POST, PUT and PATCH. For GET and DELETE it is not sent
  • Picker: in the editor there is Pick an API endpoint with the operations from the OpenAPI specification. Internal operations are not listed. If you pick one, Tillor fills in the method, path and an example body
  • Failure: a 2xx response continues through Succeeded. An error status, including a 404 for a path that does not exist, goes through Failed

The response in later steps

After the request these variables are available:

VariableContent
{response.status}The HTTP status code
{response.body}The first 4000 characters of the response as text
{response.data}The response as JSON. null if it is not JSON, or if it is longer than 100,000 characters. You address lists with an index, for example {response.data.items.0.id}

A second API request in the same flow replaces response. The variables work as described under Variables and templates.

The structure of a flow

You can also create and change automations through the API. The operations are in OpenAPI under the tag automations. Reading requires the permission automations:read, creating and changing automations:write (or all permissions). The Workflow user itself can never call them. It may be easier to build a flow in the editor and read it as an example.

A new automation is always disabled. Enabling it through enabled does not work as long as the flow has open problems, and an enabled flow cannot be saved with problems.

The definition consists of steps (nodes), lines (edges) and optionally notes (notes). Maximum 100 steps, 200 lines and 50 notes.

{
  "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
    }
  ]
}

A note (notes) is an explanation on the canvas. It is never executed and has no lines. text is at most 2000 characters, width and height are in pixels.

A line has a sourceHandle: the exit of the step it comes from.

Step (type)Exits (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, controllerCommandout
customerMessageSend, reservationSetStatus, invoiceBook, paymentLinkCreate, mandateRequest, mandateCollect, consumptionInvoiceout, skipped
conditionyes, no
switchThe id of each branch, plus default
openingHoursopen, closed
throttlepass, blocked
taskFind, documentFind, recordLookupfound, notFound
forEacheach, done
waitUntilmet, timeout
waitForEventreceived, timeout
approvalapproved, rejected, timeout
webhookSend, httpRequestsuccess, failed

Every action except webhookSend and httpRequest gets an extra exit error as soon as continueOnError in the config is set to true. Without that setting a failed action stops the run. With that setting the flow continues through error, with {error.message} and {error.step} as variables for the steps after it. The exit skipped is used when a step had nothing to do, and out is then the ordinary exit.

For forEach, the exit each starts a separate run per item for the steps after it, and the exit done is the ordinary continuation of the flow itself.

An exit may lead to several steps. The run itself continues with the first line and starts a separate run for every other line with the same data, which runs in parallel (like the exit each of forEach). Lines that form a loop are refused in the editor. A flow has exactly one trigger.

Settings per step

The fields under config are the fields from the editor. Text fields accept variables between curly braces.

StepFields in config
triggersource (event, webhook, invoiceOverdue, dateReached, daily or cron), webhookVersion (for webhook: text, increase it to replace the address), eventKey, date (for dateReached: a path record.field to a DateTime column of a model that an event sends in full, such as reservation.checkIn or contact.birthday, or to a declared date key in a JSON column, such as document.userMetadata.insuranceExpiryDate; old names such as reservationCheckIn keep working), dateOffset ({ "amount", "unit" }), dateDirection (before or after), dateTime (exact: the hour in the date, or fixed: the calendar day of the date in the organisation's time zone, at dateHour and dateMinute, text such as "9" and "0"; the offset counts on the clock, so across summer and winter time), repeatYearly (boolean: only day and month count, every year), hour and minute (text, for example "8" and "0", minute 0 to 59 inclusive), cron (five fields; the weekday may be 1#2 or 1L, a sixth field W2:2026-10-05 repeats every two weeks), conditions, oncePerRecord (boolean)
conditionconditions
switchfield (a variable path without braces), cases: a list of { "id", "key", "value" } with key as the name of the branch and value as the value the field must equal. Maximum 8 branches
delaymode (duration or untilDate), duration and offset as { "amount", "unit" } where unit is seconds, minutes, hours, days or weeks, dateField (variable path), direction (before or after)
taskCreatetitle, description, assigneeIds, labelIds, assignActor, priority (LOW, MEDIUM, HIGH or URGENT), viewId, laneId, subtasks (one per line), dueIn ({ "amount", "unit" }), linkRecords
taskSetStatustaskId, status (PENDING, IN_PROGRESS, WAITING or COMPLETED)
taskFindtitle (exact title), linkedToRecords, onlyOpen, onlyTopLevel (booleans)
documentFindcustomerId (template), documentType (INSURANCE_POLICY, INSURANCE_PAYMENT_PROOF, IDENTITY_CARD, SEPA_MANDATE, OTHER or empty), onlyActive, onlyValid (booleans). Result under foundDocument
documentUpdatedocumentId (template), documentType (empty keeps the type), status (ACTIVE, ARCHIVED or empty)
taskSetPrioritytaskId, mode (raise, lower or set), priority (for set), maxPriority (for raise)
taskUpdatetaskId, title, assigneeIds, assignActor, assigneeMode (add or replace), labelIds, dueIn ({ "amount", "unit" } where unit is minutes, hours, days or weeks)
invoiceReminderSend, invoiceSend, invoiceBookinvoiceId
invoicePrintinvoiceId, printer (a4, pos or tablet)
paymentLinkCreateinvoiceId, method (any, BANCONTACT, IDEAL, CREDITCARD or PAYCONIQ)
mandateRequestcustomerId, mode (request or reminder)
mandateCollectinvoiceId, delayDays (text, default "0", at most 7)
consumptionInvoicecustomerId, sendEmail, sendSms
occupationExtendInvoiceoccupationId, includeConsumption
customerMessageSendcustomerId, subject, body, sendEmail, sendSms, sendWhatsApp
staffNotifyuserIds, notifyActor, title, body, sendPush, sendEmail
commentCreatecontent, target (event or task), taskId (for task)
inboxReplythreadId, body, keepAssistant
inboxTagthreadId, tagIds, archive
customerUpdatecustomerId, status (keep, DEFAULT, PREFERRED, VIP, WARNING or BLACKLISTED), notes
reservationSetStatusreservationId, action (checkIn, checkOut or cancel)
accessMethodCreatecustomerId, type (QR_CODE, LPR, PIN_CODE or NFC), value, periodFrom, periodTo (variables with a date, empty is no limit)
accessMethodUpdatescope (one or customer), accessMethodId (for one), customerId (for customer), status (keep, ACTIVE, SOFT_BLOCKED, BLOCKED or INACTIVE), periodTo
accessMethodSendQraccessMethodId, sendEmail, sendSms, sendWhatsApp, print (none, label or thermal)
accessPointControlaccessPointId, action (open or close)
meterSwitchtarget (terrain or meter), terrainId (for terrain), meterId (for meter), state (on or off)
nfcTagUpdatecustomerId, action (block, unblock, topUp or lowBalance), amount (for topUp, in euros per tag)
deviceCommanddeviceId, command (wake, sleep, reload, identify, testSound or playSound), url (for playSound: the path of an uploaded sound, as the editor fills it in, or an https link to an mp3, wav or m4a file)
deviceDisplaykind (pdf or image), url (https link). Appears on every tablet that is set up as a display, such as an invoice PDF
controllerRelaytarget (controller or event: the controller of the event controller:input:changed that started the flow), controllerId (for controller), relay (text "1" to "6"), action (pulse, on or off), durationSeconds (for pulse, text, 0.001 to 3600 with decimals such as "0.5"; the relay drops out again by itself)
controllerCommandtarget and controllerId as for controllerRelay, command (reboot, rebootExtension, otaUpdate, forceOtaUpdate, extensionOtaUpdate or refreshSettings)
deviceSlideshowscope (displays or device), mode (screensaver or background), source (feed or urls). For feed: itemsPath, imageField, optional filter and sort fields
openingHours, waitUntilOpen, waitUntilClosedNo fields
throttlewindow ({ "amount", "unit" } where unit is minutes, hours, days or weeks), key (empty is per record that started the flow)
recordLookupmodel (Customer, Reservation, Occupation, Invoice, Payment, Terrain, Task, Document, AccessMethod, NfcTag, Meter, InboxThread or InboxMessage), recordId. The result is under found plus the model, such as foundCustomer
forEachsource (customerAccessMethods, customerNfcTags, customerOpenInvoices, customerUpcomingReservations, customerOccupations, customerDocuments (only ACTIVE) or terrainMeters), parentId (ID of the customer or the terrain). At most 100 items, the item is under item
waitUntilconditions, interval and timeout ({ "amount", "unit" } where unit is minutes, hours, days or weeks; the interval is at least 5 minutes)
waitForEventeventKey, sameRecord (boolean), timeout ({ "amount", "unit" })
approvaltitle, description, assigneeIds, timeout ({ "amount", "unit" }). The task is under approvalTask.id
Every action except webhookSend and httpRequestcontinueOnError (boolean)
webhookSendwebhookId, event, payload (JSON as text)
httpRequesttarget (tillor or custom), method, path (for tillor), url (for custom), headers (list of { "id", "key", "value" }), body (JSON as text)

conditions has the form { "match": "all" or "any", "rules": [...] }. Each rule is { "id", "field", "operator", "value", "values" }. operator is is, isNot, contains, greaterThan, lessThan, isOneOf, isEmpty or isNotEmpty. You only use values with isOneOf. An empty list of rules lets everything through.

Start an automation with a webhook

An automation with the trigger webhook starts on a POST to its own address:

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"}'
  • The full address is in the editor at the trigger, or via GET /automations/{id}/webhook-url (with automations:read). The token is the secret; a header for authentication is not needed
  • The JSON body becomes the variable body, the query string query, and the time receivedAt
  • The answer is 200 with { "accepted": true } as soon as the run has started. A wrong, replaced or disabled address gives 404, a body above 100 KB gives 400
  • Many calls shortly after each other are not refused: every valid call starts a run. Use Ignore repeats at the trigger if the same call can arrive twice, see Ignore repeats

An automation cannot call its own webhook through an API request to the Tillor API.

Choose a decision through the API

The task of an Ask for a decision step has one action per option you set in the step. Fetch the task to see the actions with their id, and choose one with POST /tasks/{id}/actions/{actionId}/choose. If the option asks for a note, send it along as { "note": "..." }. The choice is final: it completes the task and continues the automation along the exit of that option. Assigned colleagues (also through a group) and administrators may choose. See the OpenAPI for the details.

Read more

Was this page helpful?

On this page