Event rules.
An event rule does something when something happens: a file arrives, a package is received, a transfer fails, or a clock ticks. A rule can run a script, call a webhook, send a message, move or copy files, send files on, or write a checksum file.
Plans
Event rules are part of automation, which every Team, Business and Enterprise license includes. Automation is included. There is no separate workflow product or add-on to buy.
In Free mode a rule counts as the one free job. Event rules, hot folders and sync jobs share that one job. When a license ends or drops to Free mode, rules over the limit pause. They are not deleted. They start again when you load a license that includes automation.
Rules run after the virus scan, so a rule never touches a file that has not passed it.
Trigger, conditions, steps
Every rule has the same shape. A trigger says what starts it. Conditions narrow it down: all of them must be true. Steps run in order, and a later step can use the output of an earlier one.
Administrators can create any rule. Other users can create rules that have no command step, on folders they own.
Triggers
A rule starts on one of these events:
| Event | Sent when |
|---|---|
file.arrived | A file or folder is released from staging into a folder. |
package.received | A package is ready for its recipients. |
receive_link.submitted | All files of one receive-link submission are released. |
transfer.completed | A transfer ends successfully. |
transfer.failed | A transfer ends with an error. |
job.completed | A hot folder or sync job run ends successfully. |
job.failed | A hot folder or sync job run fails. |
scan.blocked | The virus scanner blocks a file. |
schedule.tick | The rule's schedule fires. |
manual | An administrator or the API starts the rule. |
speedcheck.degraded | A scheduled speed check finds a location more than 30% slower than its own average, or a limit below your license. |
Each event is one JSON object with "event_version": 1. It
carries an event ID, the type, the time, the server ID, the actor (a
user, an outside email address, or the system) and paths. Paths are
always relative to the storage root, never full paths on the server.
Farwing keeps events for 30 days, so you can replay a run to debug a
rule.
Wait until the file stops changing
The file.arrived trigger has a stable for setting,
in seconds. The default is 30. A file starts the rule only after it has
stopped changing for that long. It uses the same logic as hot folders,
so a file that another program is still writing is not picked up half
done. Raise the number if your source writes in bursts with long pauses.
Schedules
The schedule.tick trigger offers a schedule picker with
three forms:
- Every N minutes.
- Daily at a time you choose.
- Weekly, on the days and at the times you choose.
For anything else, type a cron line instead of using the picker.
Conditions
All conditions on a rule must be true for it to run.
| Condition | Matches |
|---|---|
| Path | A pattern, such as incoming/**/*.mov. |
| File type | The extension of the file. |
| Size | Above or below a value you set. |
| User or group | The person who caused the event, or a group they belong to. |
| Sender email domain | The domain of the sender's address, such as example.com. |
| Form field value | The answer to a custom field on a receive link. |
| Package recipient | A recipient of the package. |
Steps
| Step | What it does |
|---|---|
| Run a command | Runs a command or script on the server. Administrators only. See Run a command. |
| Call a webhook | Sends the event as a JSON POST to a URL. See Webhooks. |
| Notify | Sends an email, a Slack message or a Microsoft Teams message. Slack and Teams use their incoming-webhook URLs. The text comes from a template. |
| Move, copy or rename | Moves, copies or renames the file. The destination comes from a template, for example projects/{{form.project}}/{{file.name}}. |
| Send to another Farwing server | Sends the file to a second Farwing Server, the way a sync job does. |
| Send as a package | Sends the files as a package to users or email addresses, with a message from a template. |
| Write a checksum file | Writes a SHA256SUMS file next to the files, or a .b3 file for BLAKE3. |
| Delete | Deletes the source file. The rule will not save until you tick the confirm switch. |
Template values
Names, messages, destinations and URLs in a step are templates. A template can insert values and nothing else. These are all the values:
| Value | Is replaced by |
|---|---|
event.* | Any field of the event. |
{{file.path}} | The file's path, relative to the storage root. |
{{file.name}} | The file's name with its extension. |
{{file.stem}} | The file's name without its extension. |
{{file.ext}} | The file's extension. |
{{file.size}} | The file's size. |
{{actor.email}} | The email address of the person or sender who caused the event. |
{{actor.name}} | Their name. |
{{form.<field name>}} | The answer to a custom field on a receive link. |
{{link.name}} | The name of the receive link. |
{{package.subject}} | The subject of the package. |
{{date}} | The date. |
{{time}} | The time. |
{{steps.<step name>.output}} | The output of an earlier step: the last 4 KB of a command's standard output, or a webhook's response body. A response that is JSON is parsed, so you can use its fields. |
Run a command
- A command runs as the server's own non-root user, in a working folder made for that run.
- It does not run through a shell. An administrator can turn on Run through shell for a step when it needs one.
- The event, as JSON, arrives on the command's standard input.
- The paths given to a command are full paths inside the storage root, which the command can read.
- A step may run for 1 hour by default. An administrator can change the limit.
- Secrets you use in a step, such as webhook tokens and Slack URLs, are stored encrypted. They are hidden in the portal after you save them, and shown as
***in logs and run records.
Environment variables
The main values of the event are also set as FARWING_*
environment variables for the command. These two are always set:
| Variable | Holds |
|---|---|
FARWING_FILE_PATH | The full path of the file, which the command can open. |
FARWING_EVENT_TYPE | The event type, such as file.arrived. |
Everything else about the event is in the JSON on standard input.
Scripts in Docker
In Docker, keep your scripts in a folder on the host and mount it at
/scripts. Add one line to the volumes of your compose file:
volumes:
- /data:/data
- /srv/files:/files
- /srv/scripts:/scripts
A script that logs each arrival looks like this. Make it executable on
the host, then use /scripts/log-arrival.sh as the command
of the step.
#!/bin/sh
# /scripts/log-arrival.sh
# Appends one line to a log for every file a rule hands to it.
echo "$FARWING_EVENT_TYPE: $FARWING_FILE_PATH" >> /data/arrivals.log
chmod +x /srv/scripts/log-arrival.sh
The Farwing image is small on purpose and does not include tools such as ffmpeg. Adding tools to the image shows how to add one. To run a tool on another machine instead, use a webhook step to call a service there.
Webhooks
A webhook step sends a POST with a JSON body. The body holds
the event, the rule, the run ID and the outputs of the steps that have
run so far.
- Any 2xx status counts as success.
- The call times out after 30 seconds.
- A failed call retries before the step fails. See Retries and failures.
- Farwing blocks calls to the server's own private network addresses (
10.*,192.168.*,127.*, link-local addresses and similar) unless an administrator allows them in settings. To call a service on your own network, ask an administrator to allow it.
Checking a webhook signature
Every webhook call carries a Farwing-Signature header. Its
value is sha256= followed by the lowercase hex of an
HMAC-SHA256 of the exact request body, keyed with the rule's secret.
Check it before you trust the call, so you know it came from your
server. Three rules apply:
- Compute the HMAC over the raw bytes of the body, before any JSON parsing. Parsing and writing the JSON again can change the bytes.
- Compare with a constant-time function, as the examples do. A plain
==can leak how much of the signature matched. - Refuse the call with 401 when the signature is missing or wrong.
Python
This receiver uses only the standard library. Run it with python3 hook.py.
import hashlib
import hmac
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = b"the-rule-secret" # the secret of the rule that calls this URL
class Hook(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
given = self.headers.get("Farwing-Signature", "")
expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected.encode(), given.encode()):
self.send_response(401) # not from this server: refuse
self.end_headers()
return
# The signature matched, so the body is the exact bytes Farwing sent.
print(body.decode())
self.send_response(204)
self.end_headers()
HTTPServer(("", 8080), Hook).serve_forever()
Node.js
This receiver uses only Node.js itself. Run it with node hook.js.
const crypto = require("node:crypto");
const http = require("node:http");
const SECRET = "the-rule-secret"; // the secret of the rule that calls this URL
http
.createServer((req, res) => {
const chunks = [];
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => {
const body = Buffer.concat(chunks);
const expected = Buffer.from(
"sha256=" + crypto.createHmac("sha256", SECRET).update(body).digest("hex"),
);
const given = Buffer.from(String(req.headers["farwing-signature"] || ""));
const ok = given.length === expected.length && crypto.timingSafeEqual(given, expected);
if (!ok) {
res.statusCode = 401; // not from this server: refuse
return res.end();
}
// The signature matched, so the body is the exact bytes Farwing sent.
console.log(body.toString("utf8"));
res.statusCode = 204;
res.end();
});
})
.listen(8080);
Try it
With a receiver running, this signs a small body and sends it. A correct
signature returns 204. Change one character of the body
after signing and the receiver returns 401.
BODY='{"test":true}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "the-rule-secret" -r | cut -d' ' -f1)
curl -i -X POST http://localhost:8080 -H "Farwing-Signature: sha256=$SIG" --data-binary "$BODY"
Retries and failures
- Retries: each step can retry 0 to 5 times, waiting longer between tries. Webhooks default to 3 and commands to 0.
- On failure: choose per step whether the run stops or goes on to the next step.
- Failure email: Farwing emails the rule's owner when a run fails. This is on by default.
- Limits: by default a rule runs at most 2 times at once, and the whole server runs at most 8. Extra runs wait in line.
Test before you trust it
- Test with a sample event. Pick a real past event or a made-up one.
- Dry run. Shows what each step would do, with the templates filled in, and does nothing.
- Re-run. Runs any past run again.
Start with a dry run on a real event. Check the destinations and the messages, then switch the rule on.
Run history
Every run has a record. The run history page filters by rule, status and date, and keeps runs for 90 days. A record shows:
- The event that started the run and whether the conditions passed.
- For each step: its status, when it started and ended, the exit code or HTTP status, and its output. Output is the last 64 KB of standard output and standard error.
A run is queued, running, done, failed, skipped or interrupted. If the server stops during a run, Farwing marks the run interrupted when it starts again. It does not run it again unless the rule says to.
Audit and API
- Admin → Audit records every rule change and every manual start.
- The REST API creates, changes, deletes, enables and disables rules, starts a rule by hand, lists runs and returns one run. The API is part of every paid plan.
Ready-made rules
The New rule screen offers six rules to start from. Choose one, change the folder, address or URL to yours, run a dry run, then switch it on.
Convert new videos to small H.264 preview copies
When a video arrives, this rule runs ffmpeg to make a small H.264 preview copy, so people can watch it without downloading the original. It needs ffmpeg, which is not in the Farwing image. Add it first with Adding tools to the image. Because it contains a command step, only an administrator can create it.
Write a SHA-256 checksum file for each received package
When a package is received, this rule writes a SHA256SUMS
file next to its files. Use it when your clients or your own archive
need a checksum list to verify files later.
Copy everything from a receive link to an S3 folder
When a receive link submission is complete, this rule copies its files to a folder on an S3 storage root. Add the S3 storage in Admin → Storage first, then point the copy step at the folder you want.
Post to Slack when a package arrives
When a package is received, this rule posts a message to a Slack channel. Create an incoming-webhook URL in Slack, paste it into the notify step, and edit the message. Farwing stores the URL encrypted and hides it after you save.
Forward new files in a folder to another Farwing server
When a file arrives in the folder you choose, this rule sends it to a second Farwing Server. Set the folder in the path condition and the other server in the send step.
Email a daily list of received files
Once a day, this rule emails a list of the files your server received. Choose the time in the schedule and the address in the notify step. It needs email set up on the server.