Find a file
2026-10-04 21:09:07 +02:00
docs Initial commit 2026-10-04 21:09:07 +02:00
src/nextcloud_calendar_webhook Initial commit 2026-10-04 21:09:07 +02:00
.gitignore Initial commit 2026-10-04 21:09:07 +02:00
.style.yapf Initial commit 2026-10-04 21:09:07 +02:00
LICENSE Initial commit 2026-10-04 21:09:07 +02:00
pyproject.toml Initial commit 2026-10-04 21:09:07 +02:00
README.md Initial commit 2026-10-04 21:09:07 +02:00

📅 Nextcloud Calendar Webhook

A tiny, self-hosted bridge that turns Nextcloud calendar events into beautiful ntfy notifications.

When a calendar event is created or updated in Nextcloud, this service receives the webhook payload, extracts the event from its iCalendar data, formats it into a human-friendly notification, and sends it to an ntfy topic.

Simple. Lightweight. No database. No frontend. Just calendars → notifications. ✨

✨ How it works

flowchart LR
    NC["☁️ Nextcloud<br/>Calendar"]

    WH["📡 Calendar Webhook"]

    APP["🐍 Nextcloud Calendar Webhook<br/><br/>Flask<br/>iCalendar parser"]

    NTFY["🔔 ntfy<br/>Notification Server"]

    DEV["📱 Your Devices"]

    NC -->|Calendar event| WH
    WH -->|POST /webhook| APP
    APP -->|Formatted Markdown<br/>notification| NTFY
    NTFY -->|Push notification| DEV

    classDef nextcloud fill:#0082c9,stroke:#005b8f,color:#fff,stroke-width:2px
    classDef webhook fill:#6366f1,stroke:#4338ca,color:#fff,stroke-width:2px
    classDef app fill:#3776ab,stroke:#245a80,color:#fff,stroke-width:2px
    classDef ntfy fill:#ff6b35,stroke:#c94d20,color:#fff,stroke-width:2px
    classDef device fill:#2d2d2d,stroke:#111,color:#fff,stroke-width:2px

    class NC nextcloud
    class WH webhook
    class APP app
    class NTFY ntfy
    class DEV device

The service exposes two endpoints:

Endpoint Method Purpose
/webhook POST Receives Nextcloud calendar webhooks
/health GET Simple health check

🔔 Notifications

Calendar events are converted into Markdown notifications containing the useful bits of the event:

  • 📅 Event title
  • 🕐 Date and time
  • 📁 Calendar name
  • 📍 Location
  • 👤 Organizer
  • 📝 Description
  • All-day event information

For example:

📅 Team Meeting

Monday, 12 October 2026 · 14:00–15:00
📁 Work
📍 Meeting Room 42
👤 mailto:alice@example.com

Discuss the upcoming release.

The notification title is automatically set to the calendar event's summary, and the calendar tag is attached to the ntfy notification.

🚀 Getting started

Requirements

  • Python 3.10+
  • A Nextcloud instance with calendar/webhook support
  • An ntfy server or access to ntfy.sh
  • An ntfy topic and authentication token

1. Clone the repository

git clone <repository-url>
cd nextcloud-calendar-webhook

2. Create a virtual environment

python3 -m venv .venv
source .venv/bin/activate

On Windows:

.venv\Scripts\Activate.ps1

3. Install the project

For normal use:

python -m pip install .

For development, including documentation and YAPF:

python -m pip install -e ".[dev,docs]"

4. Configure ntfy

The service is configured entirely through environment variables.

export NTFY_URL="https://ntfy.example.com/my-calendar"
export NTFY_TOKEN="your-ntfy-token"

The following variables are required:

Variable Description
NTFY_URL Full URL of the ntfy topic to publish notifications to
NTFY_TOKEN Bearer token used to authenticate with ntfy

⚠️ Both variables are required when the application starts.

5. Start the application

The Flask application listens on port 8080:

python -m nextcloud_calendar_webhook.scripts.listener

Or, if the listener console command is configured:

listener

The service will be available at:

http://localhost:8080

Check that it is alive:

curl http://localhost:8080/health

Expected response:

OK

🔗 Nextcloud configuration

Configure a Nextcloud calendar webhook to point at:

https://your-domain.example/webhook

The service expects the webhook payload to contain the calendar event data produced by Nextcloud, including the iCalendar representation of the event.

The relevant structure is approximately:

{
  "event": {
    "calendarData": {
      "{DAV:}displayname": "My Calendar"
    },
    "objectData": {
      "calendardata": "BEGIN:VCALENDAR\r\n..."
    }
  },
  "user": {}
}

The service extracts the first VEVENT component from the supplied iCalendar data.

🐳 Running behind a reverse proxy

For a production deployment, it is recommended to put the Flask application behind a proper WSGI server and reverse proxy rather than exposing Flask's development server directly.

A typical setup might look like:

flowchart LR
    NC["☁️ Nextcloud"]

    RP["🔒 Reverse Proxy<br/>nginx / Caddy<br/><br/>HTTPS"]

    APP["🐍 Calendar Webhook<br/><br/>Flask :8080"]

    NTFY["🔔 ntfy"]

    NC -->|POST /webhook| RP
    RP -->|HTTP| APP
    APP -->|HTTPS| NTFY

    classDef cloud fill:#0082c9,stroke:#005b8f,color:#fff,stroke-width:2px
    classDef proxy fill:#059669,stroke:#047857,color:#fff,stroke-width:2px
    classDef app fill:#3776ab,stroke:#245a80,color:#fff,stroke-width:2px
    classDef notify fill:#ff6b35,stroke:#c94d20,color:#fff,stroke-width:2px

    class NC cloud
    class RP proxy
    class APP app
    class NTFY notify

This also gives you HTTPS termination, which is important when receiving webhooks from your Nextcloud instance.

🛠️ Development

Install the development dependencies:

python -m pip install -e ".[dev,docs]"

Formatting

The project uses YAPF for Python formatting.

Format the source tree:

yapf -ir src/

Or check what YAPF would change:

yapf -dr src/

The formatter configuration lives in pyproject.toml.

Project layout

.
├── pyproject.toml
├── README.md
├── src/
│   └── nextcloud_calendar_webhook/
│       ├── __init__.py
│       └── scripts/
│           ├── __init__.py
│           └── listener.py
└── docs/
    ├── conf.py
    └── index.md

The project uses the modern Python src/ layout to keep the importable package separate from project-level files.

📚 Documentation

Documentation is built using:

Build the documentation with:

sphinx-build -b html docs/ docs/_build/html

Then open:

docs/_build/html/index.html

🔐 Security

The /webhook endpoint is intended to be reachable by your Nextcloud instance.

Because the service forwards calendar information to ntfy, you should:

  • Run it behind HTTPS.
  • Keep the NTFY_TOKEN secret.
  • Avoid exposing the service unnecessarily to the public internet.
  • Consider restricting access to the webhook endpoint to your Nextcloud server where practical.
  • Use a dedicated ntfy topic/token for calendar notifications.

🧩 Dependencies

The runtime is deliberately small:

  • Flask — HTTP webhook server
  • icalendar — parsing iCalendar/VEVENT data
  • requests — sending notifications to ntfy

Development and documentation dependencies are kept separate from the runtime dependencies.

📜 License

MIT license.


Made with ☕, Python, and a healthy dislike of missing calendar notifications.