Featured image of post Notifikasi Sonarr Melalui Telegram Dengan Webhook FastAPI

Notifikasi Sonarr Melalui Telegram Dengan Webhook FastAPI

Tutorial membuat webhook FastAPI di Docker agar Sonarr mengirimkan notifikasi Telegram secara otomatis saat episode di-import, dengan format pesan kustom dan verifikasi secret header.

Tujuan kita kali ini adalah automation agar saat Sonarr selesai download dan melakukan import episode baru, kita otomatis mendapatkan notifikasi melalui Telegram. Dengan ini kita tidak perlu membuka dashboard Sonarr ataupun cek secara manual.

Kita akan membuat server webhook sederhana dengan FastAPI, menjalankannya di Docker, kemudian menghubungkannya ke Sonarr.


Kenapa Tidak Menggunakan Fitur Bawaan Sonarr?

Sonarr punya notifikasi Telegram bawaan. Tapi jika menggunakan fitur itu, format pesannya sudah ditentukan oleh Sonarr dan kita tidak bisa membuat notifikasi yang customized. Kalau kita mau menggunakan notifikasi yang customized, atau yang lebih canggih seperti melampirkan poster, link yang jika di klik membuka Jellyfin, dsb, kita butuh server sendiri.


Alur Secara Umum

1
2
3
4
Episode selesai diunduh di Sonarr
  → Sonarr mengirim POST request ke server kita
    → Server kita memverifikasi request-nya
      → Server memformat pesan dan mengirimnya ke Telegram

Stack yang Digunakan

  • FastAPI + Uvicorn – web framework dan server-nya
  • httpx – untuk mengirim HTTP request ke API Telegram
  • python-dotenv – untuk membaca credentials dari file .env
  • Docker – supaya semuanya berjalan rapi

Struktur Folder

1
2
3
4
5
6
7
8
9
/home/stevelaurensius/dockerconfig/webhook-bot/
├── app/
│   ├── main.py
│   ├── sonarr.py
│   └── notify_telegram.py
├── .env
├── Dockerfile
├── docker-compose.yml
└── requirements.txt

Buat Struktur Folder

1
sudo mkdir -p /home/stevelaurensius/dockerconfig/webhook-bot/app

Flag -p langsung membuat semua folder, termasuk folder parent.


requirements.txt

Buat file di /home/stevelaurensius/dockerconfig/webhook-bot/requirements.txt:

1
2
3
4
fastapi
uvicorn
httpx
python-dotenv

File ini hanya teks biasa yang isinya nama package yang kita perlukan. Tidak ada yang terinstall di host dan file ini akan dibaca oleh Docker saat membangun image-nya.


Dockerfile

Buat file di /home/stevelaurensius/dockerconfig/webhook-bot/Dockerfile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

(Jangan lupa cek dulu apakah port 8000 masih bisa digunakan atau tidak)

Mari kita pelajari baris per baris dari Dockerfile tersebut.

FROM python:3.12-slim

Kita menggunakan Python versi slim karena ukurannya lebih kecil dan tidak berisi banyak tool yang tidak kita perlukan.

WORKDIR /app

Semua command selanjutnya akan dijalankan di dalam folder /app di dalam container.

COPY requirements.txt . dan RUN pip install

Dua command ini akan copy file requirements.txt ke dalam image, lalu menginstall semua package-nya. Proses instalasi terjadi di dalam image, bukan di host.

COPY app/ .

Copy semua kode Python kita ke dalam image.

CMD […]

Perintah yang dijalankan saat container dijalankan. Kita meminta uvicorn untuk menjalankan aplikasi main:app (objek app di dalam main.py) pada port 8000.


docker-compose.yml

Buat file di /home/stevelaurensius/dockerconfig/webhook-bot/docker-compose.yml:

1
2
3
4
5
6
7
8
9
services:
  webhook-bot:
    build: .
    container_name: webhook-bot
    restart: unless-stopped
    ports:
      - "8000:8000"
    env_file:
      - .env

.env

Buat file di /home/stevelaurensius/dockerconfig/webhook-bot/.env:

1
2
3
BOT_TOKEN=token_bot_telegram_kamu
CHAT_ID=chat_id_kamu
WEBHOOK_SECRET=masukkan_random_string

WEBHOOK_SECRET adalah kode yang akan kita gunakan untuk melakukan verifikasi bahwa request yang masuk benar-benar berasal dari Sonarr, bukan dari sumber lain.


app/notify_telegram.py

File ini berisi satu function saja: mengirim pesan ke Telegram. Kita buat independen supaya bisa dipanggil dari mana saja. Jika nanti kita menambah notifikasi dari aplikasi lain, fungsi ini tetap bisa dipakai ulang.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import httpx
import os
from dotenv import load_dotenv

load_dotenv()

BOT_TOKEN = os.environ.get("BOT_TOKEN")
CHAT_ID   = os.environ.get("CHAT_ID")

def send_message(text: str) -> dict:
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    response = httpx.post(url, json={"chat_id": CHAT_ID, "text": text}, timeout=10)
    response.raise_for_status()
    return response.json()

app/sonarr.py

Di file ini kita menulis logic khusus Sonarr. Kita memakai APIRouter supaya route tidak menumpuk di main.py. Nanti kalau menambah Radarr, cukup buat file baru dan daftarkan router-nya di main.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from fastapi import APIRouter, Request, Header, HTTPException
from notify_telegram import send_message
import os

router = APIRouter()

WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET")

@router.post("/webhook/sonarr")
async def sonarr_webhook(request: Request, x_webhook_secret: str = Header(None)):
    if x_webhook_secret != WEBHOOK_SECRET:
        raise HTTPException(status_code=403, detail="Forbidden")
    payload = await request.json()
    event = payload.get("eventType", "")
    if event == "Test":
        return {"status": "test received"}
    if event != "Download":
        return {"status": "ignored"}
    if payload.get("isUpgrade") is not False:
        return {"status": "ignored"}
    title   = payload.get("series", {}).get("title", "Unknown")
    season  = payload.get("episodes", [{}])[0].get("seasonNumber", "?")
    episode = payload.get("episodes", [{}])[0].get("episodeNumber", "?")
    send_message(f"New episode available: {title} S{season}E{episode}")
    return {"status": "ok"}

Beberapa hal yang perlu diperhatikan di sini:

Cek secret: Setiap request yang masuk ke webhook ini harus memiliki header X-Webhook-Secret dengan value yang sudah kita set sebelumnya di .env kita. Kalau value-nya salah, server langsung menolak dengan 403 Forbidden.

Cek eventType: Sonarr mengirimkan banyak jenis event, tidak hanya saat episode selesai download. Di bot kita kali ini, kita hanya akan memproses event Download. Event Test juga diproses secara terpisah agar tombol Test di Sonarr bisa digunakan tanpa error.

Cek isUpgrade: Kalau semua trigger notifikasi di Sonarr aktif, event Download bisa datang dari beberapa sumber:

  • On File Import mengirim isUpgrade: false (episode baru per file).
  • On File Upgrade mengirim isUpgrade: true.
  • On Import Complete tidak punya field isUpgrade sama sekali.

Tanpa filter isUpgrade, File Import dan Import Complete keduanya akan mengirimkan pesan ke Telegram untuk rilis yang sama. Kode if payload.get("isUpgrade") is not False memastikan kita hanya memproses import file baru.


app/main.py

Entry point dari aplikasi kita seperti yang sudah dijelaskan di atas. Di file ini kita hanya membuat objek FastAPI, mendaftarkan router dari sonarr.py, dan menyediakan endpoint health check.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from fastapi import FastAPI
from sonarr import router as sonarr_router

app = FastAPI()

app.include_router(sonarr_router)

@app.get("/")
def root():
    return {"status": "webhook-bot is running"}

Kalau lain kali kita mau menambahkan fungsi baru dari aplikasi lain, kita hanya perlu menambahkan dua baris kode di sini (import dan include).


Build dan Jalankan

Buka /home/stevelaurensius/dockerconfig/webhook-bot/

1
sudo docker compose up --build -d

Flag --build memberitahu Docker untuk membangun ulang image-nya. Ini diperlukan karena kita baru pertama kali menjalankannya, atau setiap kali kode kita berubah.

Verifikasi container sudah berjalan:

1
2
sudo docker ps | grep webhook-bot
sudo docker logs webhook-bot

Kalau berhasil, log-nya akan menampilkan:

1
Uvicorn running on http://0.0.0.0:8000

Test health check:

1
2
curl http://localhost:8000/
# {"status": "webhook-bot is running"}

Konfigurasi di Sonarr

  1. Buka Settings > Connect
  2. Klik + dan pilih Webhook
  3. Isi field-nya:
    • Name: Notifikasi Telegram (boleh diisi apa saja)
    • URL: http://[ip-address]:8000/webhook/sonarr
    • Method: POST
  4. Tambahkan custom header:
    • Name: X-Webhook-Secret
    • Value: nilai WEBHOOK_SECRET dari file .env
  5. Centang semua Notification Triggers
  6. Klik Test dulu, kalau muncul centang hijau, klik Save

Kita centang semua Notification Triggers agar Sonarr mengirim semua jenis event ke webhook yang kita siapkan. Filter-nya ada di webhook dan kali ini hanya event Download dengan isUpgrade: false yang dikirim ke Telegram. Kalau nantinya kita mau handle event lainnya, kita cukup ubah kode tanpa perlu kembali ke Sonarr.

Saat kita klik Test, Sonarr mengirim POST dengan eventType: Test. Server kita mengenalinya dan membalas {"status": "test received"}. Tidak ada pesan Telegram yang terkirim saat tes.


Foto cover oleh lanacodes dari Unsplash

Dibawah Lisensi CC BY-NC-SA 4.0
Dibangun dengan Hugo
Tema Stack dirancang oleh Jimmy