Tech Expert & Vibe Coder

With 15+ years of experience, I specialize in self-hosting, AI automation, and Vibe Coding – building applications using AI-powered tools like Google Antigravity, Dyad, and Cline. From homelabs to enterprise solutions.

Building an n8n Workflow to Sync Obsidian Notes Between Devices Using Syncthing Events

Automation 7 min read Published Mar 21, 2026

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/events is 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.

Previous article

Setting Up Gotify Push Notifications for Cron Job Failures Across Multiple Servers

Next article

What Is a Warmup Cache Request and How Warmup Cache Request Improves Website Performance in 2026

Leave a Comment

Your email address will not be published. Required fields are marked *

Search Articles

Jump to another topic without leaving the reading flow.

Categories

Browse more posts grouped by topic.

About the Author

Vipin PG

Vipin PG

Expert Tech Support & Services

Vipin PG is a software professional with 15+ years of hands-on experience in system infrastructure, browser performance, and AI-powered development. Holding an MCA from Kerala University, he has worked across enterprises in Dubai and Kochi before running his independent tech consultancy. He has written 180+ tutorials on Docker, networking, and system troubleshooting - and he actually runs the setups he writes about.

Stay Updated

Get new posts and practical tech notes in your inbox.

Short, high-signal updates covering self-hosting, automation, AI tooling, and infrastructure fixes.