Obsidian Webhooks
Guides GitHub Log In
EN | RU
← All Guides

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

  1. Visit obsidian-webhooks.khabaroff.studio
  2. Enter your email address
  3. Click the magic link sent to your inbox
  4. You'll land on the dashboard with your webhook URL and vault key

Step 2: Install the Obsidian Plugin

  1. Open Obsidian Settings → Community Plugins
  2. Browse and search for "obsidian-webhooks-2025"
  3. Click Install, then Enable
  4. Open the plugin settings
  5. Enter your server URL: https://obsidian-webhooks.khabaroff.studio
  6. Paste your vault key (from the dashboard)
  7. 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 with openssl 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

  1. Open http://localhost:8080 in your browser
  2. Enter your email address
  3. Click the magic link in your inbox
  4. On the dashboard, click "Create Vault Key"
  5. Give it a name (e.g., "Personal Vault")
  6. 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

  1. Obsidian Settings → Community Plugins
  2. Turn off Restricted Mode if needed
  3. Browse → Search "obsidian-webhooks-2025"
  4. Install and Enable

Step 2: Configure Connection

  1. Open plugin settings
  2. Server URL:
    • Hosted: https://obsidian-webhooks.khabaroff.studio
    • Self-hosted: http://localhost:8080 (or your domain)
  3. Vault Key: Paste the key from your dashboard
  4. 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

  1. Dashboard → "Create Vault Key"
  2. Name: "Work Vault"
  3. Copy the key

Step 2: Configure Second Vault

  1. Open your second vault in Obsidian
  2. Install the same plugin
  3. Enter the same server URL
  4. 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:

  1. Dashboard → Revoke key
  2. Create new key
  3. Update plugin settings in Obsidian
  4. 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":

  1. Verify server URL is correct
  2. Check firewall/network allows SSE connections
  3. 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:

  1. Check network stability (drops can cause retries)
  2. Verify plugin version is up-to-date
  3. Enable debug logging in plugin settings

The ACK system works like this:

  1. Server sends webhook via SSE
  2. Plugin writes file to vault
  3. Plugin sends ACK back to server
  4. 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:

  1. Check reverse proxy timeouts: Caddy/nginx may close long-lived connections
  2. Mobile sync: SSE doesn't work on mobile. Use the polling fallback in plugin settings.
  3. 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:

  1. Zapier trigger: New Email in Gmail
  2. Zapier action: Webhooks by Zapier → POST
  3. URL: http://localhost:8080/api/webhook
  4. Headers: X-Vault-Key: your-key
  5. 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:

  1. Automation Recipes: Check the 10 Automation Recipes for pre-built integrations with Zapier, Make, n8n, and IFTTT.
  2. Receive Data Guide: Read How to Receive External Data in Obsidian for a comprehensive overview of all data ingestion methods.
  3. API Reference: Read the full API docs for advanced features.
  4. 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:

Frequently Asked Questions

The hosted version takes under 2 minutes (sign up, install plugin, test connection). Self-hosted setup with Docker takes 5-10 minutes including environment configuration and vault key creation.
No. The Docker Compose setup handles everything automatically. You only need to edit environment variables in the .env file. No database management or Go knowledge required for basic operation.
Yes. The Go server is lightweight (under 50 MB RAM) and works on ARM64 architecture. Use the ARM-compatible PostgreSQL image in docker-compose.yml. A Raspberry Pi 4 with 2GB RAM can handle hundreds of webhooks per day.
Both use identical encryption (AES-256-GCM) and HTTPS transport. The hosted version adds Supabase's security layer and EU GDPR compliance. Self-hosted gives you physical control over servers, while hosted removes infrastructure management burden. Security depends on your threat model.
For self-hosted: run 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.