Self-Hosted Obsidian Webhooks: Complete Setup Guide
Step-by-step tutorial for hosted and self-hosted webhook setup with Docker — 18 min read
You can set up a private webhook server for Obsidian in under 10 minutes. This complete tutorial walks you through both the hosted and self-hosted options, with step-by-step instructions for getting your first webhook delivered to your vault.
What Are Obsidian Webhooks?
Obsidian Webhooks Server is a self-hosted webhook delivery system that lets you push content from any external service directly into your Obsidian vault. Whether you're automating note creation from Zapier, capturing data from custom scripts, or building your own integrations, this setup gives you a reliable bridge between the web and your notes. For a deeper look at the architecture, see How Obsidian Webhooks Works.
The system uses Server-Sent Events (SSE) for real-time delivery, PostgreSQL for persistent queuing, and AES-256-GCM encryption to protect your data in transit.
Prerequisites
Before you start, make sure you have:
- Obsidian Desktop (Windows, macOS, or Linux)
- Docker and Docker Compose (for self-hosted option)
- Basic terminal knowledge (running commands, editing files)
- Optional: A domain with HTTPS for production deployment
Option A: Hosted Version (Fastest Start)
The fastest way to try Obsidian webhooks is using the free hosted version on EU servers (Germany, Supabase infrastructure). This section covers the hosted option before diving into self-hosting.
Step 1: Create Your Account
- Visit obsidian-webhooks.khabaroff.studio
- Enter your email address
- Click the magic link sent to your inbox
- You'll land on the dashboard with your webhook URL and vault key
Step 2: Install the Obsidian Plugin
- Open Obsidian Settings → Community Plugins
- Browse and search for "obsidian-webhooks-2025"
- Click Install, then Enable
- Open the plugin settings
- Enter your server URL:
https://obsidian-webhooks.khabaroff.studio - Paste your vault key (from the dashboard)
- Click "Test Connection" to verify
Step 3: Send Your First Webhook
Open your terminal and send a test webhook:
curl -X POST https://obsidian-webhooks.khabaroff.studio/api/webhook \
-H "Content-Type: application/json" \
-H "X-Vault-Key: your-vault-key-here" \
-d '{
"path": "Inbox/Test Note.md",
"mode": "create",
"frontmatter": {
"date": "2026-02-26",
"source": "curl-test"
},
"content": "This is my first webhook-delivered note!"
}'
Within seconds, you should see Test Note.md appear in your vault's Inbox folder with this content:
---
date: 2026-02-26
source: curl-test
---
This is my first webhook-delivered note!
The hosted version handles all infrastructure, encryption, and delivery. For most users, this is enough. If you need full control over your data, continue to Option B.
Option B: Self-Hosted Obsidian Webhook Server
This Docker-based setup gives you complete control over your data and infrastructure.
Step 1: Clone the Repository
git clone https://github.com/khabaroff-studio/obsidian-webhooks-server.git
cd obsidian-webhooks-server
Step 2: Configure Environment Variables
Copy the example environment file:
cp .env.example .env
Edit .env with your text editor:
# Database (PostgreSQL)
DATABASE_URL=postgresql://webhooks:your-secure-password@postgres:5432/webhooks?sslmode=disable
# Encryption (generate with: openssl rand -hex 32)
ENCRYPTION_KEY=your-64-character-hex-key-here
# Server
PORT=8080
HOST=0.0.0.0
# Email (for magic link authentication)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASSWORD=your-app-password
[email protected]
# Base URL (change for production)
BASE_URL=http://localhost:8080
Important configurations:
ENCRYPTION_KEY: Generate withopenssl rand -hex 32. This encrypts queue data at rest.DATABASE_URL: Update the password to something secure.SMTP_*: Required for passwordless auth. Use Gmail app passwords or your SMTP provider.BASE_URL: Must match your domain in production (e.g.,https://webhooks.yourdomain.com).
Step 3: Launch with Docker Compose
docker-compose up -d
This starts two containers:
- postgres: PostgreSQL 15 database
- server: Go application with webhook API and SSE delivery
Verify they're running:
docker-compose ps
You should see both containers with status "Up".
Step 4: Verify Server Health
Check the API is responding:
curl http://localhost:8080/health
Expected response:
{
"status": "ok",
"timestamp": "2026-02-26T10:30:00Z"
}
Step 5: Create Your First Vault Key
- Open
http://localhost:8080in your browser - Enter your email address
- Click the magic link in your inbox
- On the dashboard, click "Create Vault Key"
- Give it a name (e.g., "Personal Vault")
- Copy the generated key (you'll need it for the plugin)
Install the Obsidian Plugin
Whether you chose hosted or self-hosted, the plugin setup is identical.
Step 1: Install from Community Plugins
- Obsidian Settings → Community Plugins
- Turn off Restricted Mode if needed
- Browse → Search "obsidian-webhooks-2025"
- Install and Enable
Step 2: Configure Connection
- Open plugin settings
- Server URL:
- Hosted:
https://obsidian-webhooks.khabaroff.studio - Self-hosted:
http://localhost:8080(or your domain)
- Hosted:
- Vault Key: Paste the key from your dashboard
- Click "Test Connection"
You should see: "Connection successful. SSE stream is active."
Step 3: Configure Delivery Options
In plugin settings, you can adjust:
- Default folder: Where webhooks create notes (default:
Inbox/) - Auto-create folders: Automatically create parent folders
- Reconnect on offline: Poll for missed webhooks after network drops
- Log level: Useful for debugging webhook delivery
Send Your First Webhook
Now for the fun part. Let's send data from any external source into your vault. For more ready-to-use examples, see 10 Automation Recipes.
Basic Webhook Structure
curl -X POST http://localhost:8080/api/webhook \
-H "Content-Type: application/json" \
-H "X-Vault-Key: your-vault-key-here" \
-d '{
"path": "Inbox/My Note.md",
"mode": "create",
"frontmatter": {
"date": "2026-02-26",
"tags": ["automation", "test"]
},
"content": "# My First Webhook\n\nThis note was created via webhook."
}'
Understanding Delivery Modes
The mode field controls how content is handled:
1. Create Mode (Default)
Creates a new note. If file exists, adds a number suffix.
{
"path": "Inbox/Daily Log.md",
"mode": "create",
"content": "Today's entry"
}
If Daily Log.md exists, creates Daily Log 2.md.
2. Append Mode
Adds content to the end of an existing note.
{
"path": "Inbox/Daily Log.md",
"mode": "append",
"content": "\n\n---\n\nNew entry at 10:30 AM"
}
Creates the file if it doesn't exist.
3. Overwrite Mode
Replaces entire file content.
{
"path": "Inbox/Daily Log.md",
"mode": "overwrite",
"frontmatter": {
"updated": "2026-02-26"
},
"content": "This replaces everything"
}
Frontmatter Handling
The server converts JSON to YAML frontmatter automatically:
Webhook payload:
{
"frontmatter": {
"date": "2026-02-26",
"tags": ["work", "project-x"],
"priority": 1,
"metadata": {
"source": "api",
"user_id": "abc123"
}
},
"content": "Note content here"
}
Result in vault:
---
date: 2026-02-26
tags:
- work
- project-x
priority: 1
metadata:
source: api
user_id: abc123
---
Note content here
Configure Multi-Vault Support
If you have multiple vaults (e.g., Personal, Work), create separate vault keys.
Step 1: Create Additional Keys
- Dashboard → "Create Vault Key"
- Name: "Work Vault"
- Copy the key
Step 2: Configure Second Vault
- Open your second vault in Obsidian
- Install the same plugin
- Enter the same server URL
- Paste the second vault key
Each vault receives only webhooks sent to its specific key.
Step 3: Send to Specific Vault
# Send to personal vault
curl -X POST http://localhost:8080/api/webhook \
-H "X-Vault-Key: personal-vault-key" \
-d '{"path": "Personal/Note.md", "content": "Personal note"}'
# Send to work vault
curl -X POST http://localhost:8080/api/webhook \
-H "X-Vault-Key: work-vault-key" \
-d '{"path": "Work/Note.md", "content": "Work note"}'
Security Checklist
Follow these best practices for production webhook server deployments.
1. Use HTTPS in Production
Never run webhooks over HTTP on the internet. Configure TLS:
Option A: Reverse Proxy (Recommended)
Use Caddy or nginx with automatic Let's Encrypt:
# docker-compose.yml addition
caddy:
image: caddy:2
ports:
- "443:443"
- "80:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
Caddyfile:
webhooks.yourdomain.com {
reverse_proxy server:8080
}
Option B: Cloudflare Tunnel
Zero-config HTTPS via Cloudflare:
cloudflared tunnel --url http://localhost:8080
2. Verify Encryption Is Active
The server uses AES-256-GCM encryption for all queued data. Verify your ENCRYPTION_KEY is set:
docker-compose exec server env | grep ENCRYPTION_KEY
Should output a 64-character hex string.
3. Rotate Vault Keys
If a key is compromised:
- Dashboard → Revoke key
- Create new key
- Update plugin settings in Obsidian
- Update any external services sending webhooks
4. Secure Your Database
Change the default PostgreSQL password in .env:
DATABASE_URL=postgresql://webhooks:use-strong-random-password-here@postgres:5432/webhooks
5. Monitor Failed Deliveries
Check server logs for issues:
docker-compose logs -f server
Look for [ERROR] lines related to delivery failures.
Troubleshooting Common Issues
Webhook Not Arriving in Vault
Check 1: SSE Connection
Plugin settings → Connection Status should show "Connected". If it says "Disconnected":
- Verify server URL is correct
- Check firewall/network allows SSE connections
- Look for errors in Obsidian Developer Console (Ctrl+Shift+I)
Check 2: Vault Key
Ensure the X-Vault-Key header in your webhook matches the key in plugin settings. Case-sensitive.
Check 3: Server Logs
docker-compose logs -f server | grep webhook
Look for the webhook ID and delivery status.
Duplicate Notes Appearing
The system uses ACK (acknowledgment) confirmation to prevent duplicates. If you see duplicates:
- Check network stability (drops can cause retries)
- Verify plugin version is up-to-date
- Enable debug logging in plugin settings
The ACK system works like this:
- Server sends webhook via SSE
- Plugin writes file to vault
- Plugin sends ACK back to server
- Server deletes webhook from queue
If step 3 fails, the server retries (causing duplicates). The plugin has built-in deduplication based on webhook ID. For a detailed look at this flow, see How Obsidian Webhooks Works.
Large Payloads Failing
The server has a 10 MB limit per note. If you're hitting this:
Solution 1: Split Large Content
Send multiple webhooks with append mode:
# Part 1
curl -X POST http://localhost:8080/api/webhook \
-H "X-Vault-Key: your-key" \
-d '{"path": "Large Note.md", "mode": "create", "content": "Part 1..."}'
# Part 2
curl -X POST http://localhost:8080/api/webhook \
-H "X-Vault-Key: your-key" \
-d '{"path": "Large Note.md", "mode": "append", "content": "\nPart 2..."}'
Solution 2: Link to External Files
Store large content elsewhere and link:
{
"path": "Index.md",
"content": "See attached: [Report](https://yourdomain.com/report.pdf)"
}
SSE Connection Keeps Dropping
If the SSE stream disconnects frequently:
- Check reverse proxy timeouts: Caddy/nginx may close long-lived connections
- Mobile sync: SSE doesn't work on mobile. Use the polling fallback in plugin settings.
- Corporate networks: Some block SSE. Try a different network to test.
The plugin automatically reconnects and polls for missed webhooks, so this is usually transparent to the user. For understanding the full pull vs push architecture, see REST API vs Webhooks.
Real-World Webhook Examples
Example 1: Zapier Integration
Create a note from Gmail:
- Zapier trigger: New Email in Gmail
- Zapier action: Webhooks by Zapier → POST
- URL:
http://localhost:8080/api/webhook - Headers:
X-Vault-Key: your-key - Payload:
{
"path": "Inbox/Email - {{subject}}.md",
"mode": "create",
"frontmatter": {
"date": "{{received_date}}",
"from": "{{from_email}}",
"tags": ["email"]
},
"content": "# {{subject}}\n\n{{body_plain}}"
}
Example 2: Daily Log from Cron
Append to a daily note every hour:
#!/bin/bash
# cron-log.sh
DATE=$(date +%Y-%m-%d)
TIME=$(date +%H:%M)
curl -X POST http://localhost:8080/api/webhook \
-H "Content-Type: application/json" \
-H "X-Vault-Key: your-key" \
-d "{
\"path\": \"Daily/${DATE}.md\",
\"mode\": \"append\",
\"content\": \"\\n- ${TIME}: System check completed\"
}"
Add to crontab:
0 * * * * /path/to/cron-log.sh
Example 3: Readwise Highlights
Forward Readwise exports:
curl -X POST http://localhost:8080/api/webhook \
-H "X-Vault-Key: your-key" \
-d '{
"path": "Books/{{book_title}}.md",
"mode": "append",
"content": "\n> {{highlight_text}}\n\n[[{{book_title}}]] - Page {{page_number}}"
}'
For more integration patterns including n8n, Make, Python, and Telegram bots, see the full 10 Automation Recipes guide. If you're integrating with AI tools like Claude or GPT, check out Connect AI Agents to Obsidian.
Next Steps
You now have a working Obsidian webhook setup. Here's what to explore next:
- Automation Recipes: Check the 10 Automation Recipes for pre-built integrations with Zapier, Make, n8n, and IFTTT.
- Receive Data Guide: Read How to Receive External Data in Obsidian for a comprehensive overview of all data ingestion methods.
- API Reference: Read the full API docs for advanced features.
- Community: Join the GitHub Discussions to share your automations.
Performance Tips
- Batch webhooks: Send multiple notes in quick succession (SSE handles concurrent delivery)
- Use append mode: More efficient than creating many small files
- Monitor queue size: Dashboard shows pending webhooks count
Backup Strategy
Your vault is already in sync (via Obsidian Sync or Git). The webhook server stores minimal state:
- Database: Backup PostgreSQL with
pg_dump - Vault keys: Export from dashboard before major changes
# Backup database
docker-compose exec postgres pg_dump -U webhooks webhooks > backup.sql
# Restore
docker-compose exec -T postgres psql -U webhooks webhooks < backup.sql
Conclusion
This tutorial covered both hosted and self-hosted setups, from initial installation to production security. The Docker-based configuration gives you a reliable automation bridge that delivers webhooks in under a second, with exactly-once guarantees and offline sync.
Whether you're capturing emails, syncing highlights, or building custom integrations, this setup scales from a single vault to multi-vault teams.
The self-hosted option (MIT license) means you control your data completely, while the hosted version (free on EU servers) gets you started in 60 seconds.
Start automating your Obsidian vault today at obsidian-webhooks.khabaroff.studio or clone the GitHub repo for self-hosting.
Additional Resources:
- GitHub Repository — Source code and API docs
- Plugin Page — Install the companion plugin
- Landing Page — Hosted version signup
- Recipes Collection — Pre-built automation templates
Frequently Asked Questions
git pull, then docker-compose pull && docker-compose up -d. The server automatically migrates database schema on startup. For hosted version, updates are automatic with zero downtime.
Other Guides
How Obsidian Webhooks Works
Architecture, setup, integration examples — all on one page.
8 min readHow to Receive External Data in Obsidian
Complete guide to sending data to Obsidian from external services using webhooks.
12 min read10 Automation Recipes
Copy-paste webhook recipes for Zapier, Make, n8n, IFTTT with JSON payloads.
11 min readConnect AI Agents to Obsidian
Integrate Claude, GPT, and custom agents with your knowledge base via webhooks.
14 min readREST API vs Webhooks
When to use pull vs push architecture for Obsidian automation.
12 min readInstall Obsidian Webhooks
Download the latest release of the plugin and server.
Download →