---
title: Workflows
description: Run a short chain of steps in the background when a record arrives, when a file is uploaded, on a test, or on a signed webhook.
sidebar:
  order: 13
---

A workflow is a short chain of steps that runs in the background. Something
starts it, each step hands its result to the next, and the run is kept so you
can see what happened.

Workflows come with some [plans](/dashboard/billing/), and while they are
being finished they also go out one site at a time. So the **Workflows** entry
is in the sidebar only for sites whose plan includes them and that we have
switched them on for. If yours does not have it, this chapter describes
something your site cannot reach yet. Nothing is lost while it is off: a site
keeps its workflows and their run history, and they start running again when it
is on.

## Build one

Choose **New workflow**. The editor takes the whole screen beside the dashboard
navigation: the card at the top left holds the workflow's name and state, and
its buttons sit at the top right. It starts on **Manual test**, so you start its
runs yourself. To run it whenever a row is added to a table instead, including
one you add on the dashboard, use **Change trigger** on the trigger node and
pick **New table record**, then choose its table. Name the draft in the card,
then open a node to configure it: double-click it, or select it and choose
**Open** from the toolbar above it.

**File uploaded** is a trigger too. It starts a run for every file put on your
site's [Disk](/dashboard/disk/), whether you uploaded it there, set it as the
favicon or the sharing picture, or your agent added it, and it has nothing to
set. The run is handed the file's name, type, size, dimensions and key, never
the picture itself.

Add a step from the **+** on a connection, the **+** after the last node, or
**Add a step** at the top right, then search the list and pick one. It lands
where you asked and opens its settings when it has any. You can have twenty:

- **Map fields** builds the object the next step receives. Its **Field mappings**
  JSON object maps each output name to a path or a literal. For example,
  `{"name":{"path":"record.data.name"},"source":{"literal":"Website"}}`.
- **Create a record** writes that object into a table you choose, so what comes
  out of your mapping has to match the fields that table asks for.
- **Update record** changes one row of a table you choose. **Find the row**
  maps table fields to the values that pick the row out, and **Change these
  fields** maps the fields to change, both written the way field mappings are.
  Fields you leave out keep their values. Exactly one row has to match: when
  several do, the step fails instead of guessing, and **When no row matches**
  decides whether a missing row fails the run or finishes it without changes.
- **Run JavaScript** runs the **Script** you write and hands the object it
  returns to the next step. The script reads `input`, which is what the step
  before it handed on, `trigger`, which is what started the run, and the files
  on your Disk through `disk.list()` and `disk.get(key)`. It cannot reach the
  network or change anything itself, and it is stopped after a second. When it
  fails, the run says what kind of error it was and on which line.
- **Send a JSON webhook** posts it to an HTTPS address. Delivery is at least
  once, so the receiver should watch the `Idempotency-Key` header and ignore a
  repeat. Redirects and private addresses are refused.

Order changes on the canvas. Drop a step onto a connection to insert it there,
drag from one node's output dot to another step's input to make that step run
next, or use **Move earlier** and **Move later** in the step's own menu. Moving
a node anywhere else, and panning or zooming, changes the layout only, which is
saved with the draft. **Delete step** takes a step out. **Preview field
mappings** opens a dialog that runs your mapping steps against a sample JSON
input and shows what each one makes of it, without saving anything or
performing any action.

**Tidy up** in the bottom-left controls puts every node back in a plain row,
beside fit, zoom and 1:1.

## Change the trigger

**Change trigger**, on the trigger node's own toolbar or in its menu, swaps what
starts a workflow without rebuilding the rest. Your steps, their settings and
the canvas layout all stay where they are.

It clears every setting on the trigger itself, so a table trigger loses the
table you picked, and picking it again later does not bring it back. The screen
names what it is about to clear and waits for you to confirm. If the new trigger
needs a setting the old one did not, the editor shows it as something to fill in
and the draft will not save until you do.

Steps you wrote against the old trigger keep running against the new one, so
check any mapping that reads from the incoming data.

The editor works on desktop and phone. On a phone the dashboard navigation
folds into a bar above the editor. The editor needs JavaScript: if it cannot
load, the page says so and asks you to reload, and your saved draft is
untouched.

## Save, activate, test

Saving keeps a draft, and a draft never runs. **Activate saved draft** publishes
it, and new runs use it from then on. A run already queued keeps the definition
it started with, so activating never changes a run in flight. **Pause workflow**
stops new runs from the trigger, and anything already queued still finishes.
**Test run** still works while a workflow is paused. **Resume workflow** turns
it back on with the version that was live before the pause, and a draft you
saved since stays a draft until you activate it.
Saving, activating, pausing and resuming each confirm with a notification in
the corner of the screen, and you stay where you are. Changes you have not
saved yet stay in the editor through activating, pausing and resuming, still
unsaved: activating publishes the saved draft, not them.

**Test run** opens a dialog for a JSON fixture, and **Queue test run** runs the
saved draft on it for real: it writes the records and sends the webhooks your
steps ask for, so give it sample data. You land on the run itself, and the test
appears in run history like any other run.

If somebody else on your site saved while you were editing, the editor says so
and keeps your work: reload to take their version, or override it with your own
draft. A draft that was in the editor stays on the page until you leave it, and
**Restore retained draft** puts it back.

**Remove** asks first, because it deletes the workflow's run history with it,
and runs still waiting in the queue will not start.

## Run history

**Run history** in the editor's card lists the latest runs with when each
started, what triggered it, and how it ended, and **Runs** on the Workflows list
shows all of them, a page at a time. Opening one shows the
version that ran (a test run says **Saved draft**), how long it took, the input that started it, and each step
with its status, how many attempts it took, its own input and output, and what
went wrong if anything did. A failed run offers **Retry failed step**, which
carries on from there.

A run keeps the values it was given as they were sent, including any password,
token or key among them, so a step that passes one on gets the real value. It
is kept for 30 days, and goes sooner if you delete the row that started it.

A row larger than a run can hold, which takes several long text fields filled
to their limit, still shows up in run history: as a failed run that names the
row and says it was too large, with nothing to retry.

## Answer a signed webhook

A workflow can also run when another system posts JSON to an address of the
workflow's own on your site, `/-/workflows/<path>`, signing every delivery.
Use **Change trigger** and pick **Signed webhook**, then swap the
other steps for a **JSON result**, because a signed webhook runs JSON results
only. Its **Result** is the JSON object the sender reads back when the run is
done. Clear it to hand back what was posted.

Saving gives the workflow its path, made from its name, and a signing secret.
The secret opens in a dialog with a **Copy** button, and that is the only time
it's shown, so put it in the system that sends the webhook before you close it.
The **Webhook path** field in the card holds the path and says the whole
address. Change it and save to move the address, which happens straight away.
As with any workflow, the path answers only once the saved draft is activated.

Keep the secret private: anybody holding it can post there as though they were
the sender. **Rotate signing secret** makes a new one and shows it the same
way. The old one stops working the moment it changes, so rotate when the
secret is lost or might have leaked, and give the sender the new one.

Changing a webhook's trigger keeps its address answering until you activate
the new version. After that the path and the secret are gone, and changing
back gives it new ones.

A caller posts JSON to the path with three headers:

- `X-Vizitka-Timestamp`, the current time in seconds, within five minutes of
  ours.
- `X-Vizitka-Delivery`, a fresh UUID for every delivery. Sending the same one
  twice, in capitals or lower case, gives back the first run instead of doing
  the work again, so a caller that retries after a timeout cannot have it done
  twice.
- `X-Vizitka-Signature`, `sha256=` followed by an HMAC-SHA256 of the method, the
  path, the timestamp, the delivery UUID and the body, joined with newlines and
  signed with your signing secret.

Anything unsigned, stale, larger than 64 KB, or not JSON is refused, and a path
answers at most 60 requests a minute and 1,000 a day.

A request that passes is accepted straight away, before the work is done, and
the answer names the run, its status, and a `status_path` the caller polls for
the result. Polling is signed the same way, with `GET` as the method and an
empty body. A `status_path` keeps answering after you pause the workflow or
change its path, since a run already accepted still finishes.
