I just set up Vikunja as task manager. I looked at a few options but decided on Vikunja based on its clean UX and simple configuration. However, there are a couple of holes out of the box that I noticed:
- No agentic integration
- No mobile push notifications
I looked around and found a couple of old conversations about solutions — options for push notifications on android, and a configuration using ntfy and NodeRed. There are some interesting approaches, but I really didn’t want to stand up new infrastructure or use new cloud services just for this. All the options depend on a cloud service like ntfy, and adding new infrastructure or local services to integrate it.
But wait! I’m running Home Assistant (HA), which opens a whole bunch of possibilities. Without much effort, I was able find a solution to both of these limitations without any new apps, infrastructure or cloud services.
A note about accessing this outside your network
For this to be useful outside the confines of your home network, you’re going to need some kind of cloud access to HA. This discusses only how to integrate Vikunja events with Home Assistant; it’s assumed that you have some solution for accessing HA from the wild already. Or you don’t care about it. Home Assistant Cloud is by far the simplest way. If you have a homelab already, it’s also easy to set up Wireguard, or use a cloud service like Tailscale. These work fine and also would give you access to Vikunja itself. Either way, I encourage getting an HA cloud subscription, though. It’s inexpensive, and your subscription fees support development of HA.
The Setup
I have a Proxmox datacenter that hosts Vikuna and HA, but I don’t think it matters much assuming everything lives in your homelab. The pieces are:
- Home Assistant running in a VM.
- Vikunja running in Docker (in an LXC that I use to host anything that’s pretty small and I don’t want to set up in its own LXC)
- Vikunja Voice Assistant which allows interacting with Vikunja via HA’s voice assistant.
- Extended Openai Conversation to integrate with my local LLM. Not specific to Vikunja Voice Assistant but works perfectly.
This blog is about getting push notifications working; I don’t have much to say about Vikunja Voice Assistant because it just worked for me with zero configuration. I already had Extened Openai Conversation installed and connected as a voice assistant.
Overview
The flow is pretty simple overall:
- Vikunja event posts to a webhook in HA
- HA webhook triggers an automation
- Automation generates a push notification to my phone
A few things need to be configured to make this work. You’ll need to replace these with the IPs of your services
- 172.16.2.100 - Home Assistant
- 172.16.2.101 - Vikunja
Step 1: The HA Automation
In automations.yaml, add a webhook trigger automation. This automation config creates the webhook itself as
/api/webhook/<your_webhook_id> — no other config is needed in HA.
1- id: vikunja-reminder-notification2 alias: Vikunja reminder notification3 triggers:4 - trigger: webhook5 webhook_id: vikunja_reminder6 allowed_methods:7 - POST8 local_only: false9 conditions: []10 actions:11 # replace with your device's notify service12 - action: notify.mobile_app_pixel_1013 data:14 title:15 "{{ trigger.json.event_name | default('Task reminder') | replace('task.', '') | replace('tasks.', '') |5 collapsed lines
16 replace('_', ' ') | title }}"17 message:18 "{{ trigger.json.data.task.title | default(trigger.json.data.tasks | map(attribute='title') | join(', ') |19 default('Task event')) }}"20 mode: singleA few notes on the template. Vikunja’s webhook body looks like this:
1{2 "event_name": "task.reminder.fired",3 "time": "2026-05-29T17:24:00Z",4 "data": {5 "task": { "title": "Call the plumber", ... },6 "project": { ... },7 "user": { ... }8 }9}So the task title is at trigger.json.data.task.title. The title template strips the task. prefix and formats it
nicely — task.reminder.fired becomes “Reminder Fired”, task.overdue becomes “Overdue”, etc. The tasks.overdue
event (for batch overdue notifications) has a data.tasks array rather than a single data.task, which the fallback
handles.
local_only: false is required because the webhook comes from the Docker container’s network, which gets NAT’d through
172.16.2.101. Even though that’s on the same LAN as HA, HA needs local_only: false to accept it — the internal
Docker bridge subnet (172.30.x.x) apparently doesn’t satisfy the “local” check.
Step 2: Configure Vikunja Webhooks
In Vikunja, go to Settings → Webhooks to add a user-level webhook (fires for all your projects):
- URL:
http://172.16.2.100:8123/api/webhook/vikunja_reminder - Events:
task.overdue,task.reminder.fired,tasks.overdue
You can also create a webhook at the project level for a wide variety of events like task creation, deletion, and so on. The configuration is exactly the same.
Step 3: The SSRF Problem
Vikunja has SSRF protection that blocks outgoing webhook requests to private IP ranges — including 172.16.0.0/12,
which covers my entire home network. So every time a reminder fired, the webhook attempt was logged as:
1level=ERROR msg="Error while handling message ...2reason_poisoned=Post "http://172.16.2.100:8123/...":3dial tcp 172.16.2.100:8123: prohibited IP address:4172.16.2.100 is not a permitted destination (denied by: 172.16.0.0/12)"There’s a config option to disable this check: outgoingrequests.allownonroutableips. Add it to your
docker-compose.yml environment block:
1services:2 vikunja:3 image: vikunja/vikunja:latest4 environment:5 # ... other config ...6 VIKUNJA_OUTGOINGREQUESTS_ALLOWNONROUTABLEIPS: 'true'With this set, Vikunja dispatches the webhook, HA receives it, and the push notification arrives.
The Template Gotcha
One more thing worth documenting. The Watermill internal event message (visible in Vikunja’s error logs) looks like
{"webhook_id":3,"payload":{"event_name":"...","data":{...}}}. This is not what Vikunja actually sends over HTTP.
The real HTTP body is just the WebhookPayload struct:
1{2 "event_name": "task.created",3 "time": "...",4 "data": { "task": { ... } }5}So the correct HA template path is trigger.json.data.task.title, not trigger.json.payload.data.task.title. Getting
this wrong produces a huge JSON blob as the fallback message, which either fails silently or produces an unusable
notification.
Final State
The automation triggers reliably for Vikunja events. The notification title shows the event type (e.g., “Reminder Fired”) and the message shows the task title. Setting reminder times on tasks works as expected — Vikunja’s reminder cron checks every minute and fires the webhook when one is due.
The only remaining limitation is that reminders require the reminder time to fall in the current minute in the user’s configured timezone. If you set a reminder and nothing fires, double-check that Vikunja’s timezone config matches your local time — the reminder cron runs in UTC by default and the timezone adjustment happens per-user at dispatch time.
More Home Assistant Integration
This configuration is only about getting push notifications for Vikunja events. I use a VPN to access Vikunja in my homelab directly on the road. If you want more direct integration, check out the Vikunja Home Assistant Integration.