Why I Built This
I’ve been running Obsidian across multiple devices for years—my main Windows desktop, a Debian LXC container on Proxmox, and occasionally an Android tablet. I already use Syncthing to keep my vault in sync between these machines because I don’t want my notes sitting in someone else’s cloud.
But syncing files is just the first step. What I really wanted was to trigger automations the moment a note changed. For example, when I add a new daily note on my phone, I want my server to pick it up and process it—extract tasks, update a database, send reminders, whatever makes sense that day.

I already had n8n running in a Docker container on the same Proxmox host. The question was: how do I make Syncthing tell n8n when something changes?
My Setup Before Starting
Here’s what was already in place:
- Syncthing installed on Windows 11, inside a Debian 12 LXC on Proxmox, and on Android via Syncthing-Fork
- Obsidian vault syncing bidirectionally across all three devices
- n8n running in a Docker container on the Proxmox host, accessible at
http://192.168.1.50:5678 - Basic familiarity with n8n webhooks and file triggers
I needed a way to detect file changes in the vault folder on the Debian LXC and send those events to n8n.
The Problem with Direct File Watching
My first instinct was to use n8n’s built-in “Local File Trigger” node. I set the path to my vault directory (/home/vipin/obsidian-vault) and tested it.
It worked—barely. The trigger fired when files changed, but it was unreliable. Sometimes it missed updates. Sometimes it fired multiple times for a single change. I realized this was because Syncthing writes files in chunks and moves temp files around during sync, which confuses simple file watchers.
I needed something that understood Syncthing’s behavior, not just filesystem events.
Using Syncthing’s REST API
Syncthing has a REST API that exposes folder states and sync events. I spent some time in the Syncthing web GUI (running at http://localhost:8384 on the Debian LXC) and found the API documentation under Actions → API.
The endpoint I cared about was /rest/events, which streams real-time events from Syncthing. Every time a folder finishes syncing, it emits a FolderCompletion event.
I tested this manually with curl:
curl -X GET "http://localhost:8384/rest/events?since=0" \
-H "X-API-Key: YOUR_API_KEY_HERE"
It returned a stream of JSON events. When I edited a note on my phone and Syncthing synced it to the server, I saw:
{
"id": 12345,
"type": "FolderCompletion",
"time": "2025-03-21T10:32:15Z",
"data": {
"folder": "obsidian-vault",
"completion": 100
}
}
Perfect. This was the signal I needed.
Building the n8n Workflow
I opened n8n and created a new workflow. Here’s what I built:
Step 1: Webhook Trigger
I added a Webhook node and set it to listen at /webhook/obsidian-sync. This gave me a URL like http://192.168.1.50:5678/webhook/obsidian-sync.
The plan was to have a script running on the Debian LXC poll Syncthing’s events API and POST to this webhook whenever a sync completed.
Step 2: Python Script to Poll Events
I wrote a small Python script to continuously poll /rest/events and trigger the webhook when the vault folder finished syncing.
#!/usr/bin/env python3
import requests
import time
SYNCTHING_API = "http://localhost:8384"
API_KEY = "your-syncthing-api-key"
FOLDER_ID = "obsidian-vault"
WEBHOOK_URL = "http://192.168.1.50:5678/webhook/obsidian-sync"
last_event_id = 0
while True:
try:
response = requests.get(
f"{SYNCTHING_API}/rest/events",
headers={"X-API-Key": API_KEY},
params={"since": last_event_id, "timeout": 60}
)
events = response.json()
for event in events:
last_event_id = event["id"]
if event["type"] == "FolderCompletion":
data = event.get("data", {})
if data.get("folder") == FOLDER_ID and data.get("completion") == 100:
# Sync completed for our vault
requests.post(WEBHOOK_URL, json={"event": "sync_complete", "folder": FOLDER_ID})
print(f"Triggered webhook at {time.strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e:
print(f"Error: {e}")
time.sleep(5)
I saved this as /home/vipin/scripts/syncthing-watcher.py and made it executable:
chmod +x /home/vipin/scripts/syncthing-watcher.py
Step 3: Running the Script as a Service
I didn’t want to manually start this script every time the LXC rebooted. I created a systemd service:
sudo nano /etc/systemd/system/syncthing-watcher.service
Contents:
[Unit]
Description=Syncthing Event Watcher for Obsidian
After=network.target [email protected]
[Service]
Type=simple
User=vipin
ExecStart=/home/vipin/scripts/syncthing-watcher.py
Restart=always
[Install]
WantedBy=multi-user.target
Enabled and started it:
sudo systemctl daemon-reload
sudo systemctl enable syncthing-watcher.service
sudo systemctl start syncthing-watcher.service
I checked the status:
sudo systemctl status syncthing-watcher.service
It was running cleanly.
Step 4: Processing in n8n
Back in n8n, I connected the webhook to a “Code” node where I could process the event data. For testing, I just logged the event and sent myself a notification.
Later, I added nodes to:
- Read the vault directory using the “Read/Write Files from Disk” node
- Filter for new or modified markdown files based on modification time
- Extract frontmatter metadata using a Code node with regex
- Send task items to a separate n8n workflow that manages my task database
The key was that the webhook fired after Syncthing finished syncing, so the files were already up-to-date on disk when n8n accessed them.
What Worked
This approach has been running for about three months now. Here’s what I like:
- The webhook fires reliably when sync completes, not during partial writes
- I have full control over what happens next—no vendor lock-in
- It’s simple enough that I can debug it when something breaks
- The Python script is under 50 lines and uses only standard libraries plus requests
I’ve used it to automatically extract tasks from my daily notes and push them to a separate task tracker I built in n8n. I’ve also experimented with sending weekly review notes through an OpenAI node to generate summaries.
What Didn’t Work
My first version of the Python script polled the API every 5 seconds instead of using the long-polling timeout parameter. This worked but hammered the Syncthing API unnecessarily. Switching to long-polling (timeout=60) reduced the load to almost nothing.
I also tried using n8n’s built-in “Execute Command” node to run inotifywait directly, but it was messier to manage than a standalone systemd service. I wanted the watcher to run independently of n8n so I could restart n8n without losing sync detection.
One limitation: this only detects when syncing finishes, not which specific files changed. If I need file-level granularity, I have to compare modification times or read Syncthing’s database. For my use case, knowing “something changed” is enough—I just scan the whole vault for recent updates.
Key Takeaways
- Syncthing’s REST API is clean and well-documented. Use it instead of trying to watch filesystem events directly.
- Long-polling on
/rest/eventsis the right way to monitor for changes—don’t poll in a tight loop. - Running the watcher as a systemd service makes it reliable and easy to manage.
- n8n webhooks are flexible enough to handle this kind of event-driven workflow, but you need to bring your own event source.
- This setup keeps everything local. No cloud services, no third-party sync APIs, no subscription fees.
If you’re already running Syncthing and n8n, this is a straightforward way to connect them. The Python script can be adapted to trigger on other Syncthing events—folder errors, device connections, whatever you need.