Wren TVDocs

Developers

Integrations and webhooks

Manage libraries from another service with the integration key, and get webhooks for downloads and streaming.

The integration API is for backend services that manage a Wren TV server automatically, such as a script that creates libraries for a new drive, or a service that reacts when a download finishes.

The integration key

Every server generates an integration key on first start (wtv_…). Find it in Manage → Settings → Integrations, or as integration_key in wrentv.yaml. Send it on every request:

httpX-WrenTV-Key: wtv_…

Endpoints

MethodPathBody
GET/api/integration/libraries
POST/api/integration/libraries{"name": "Movies", "type": "movies", "paths": ["/media/Movies"]}. type is movies or shows
POST/api/integration/libraries/{id}/scan
bashcurl -X POST https://<server>/api/integration/libraries \
     -H "X-WrenTV-Key: $KEY" \
     -d '{"name": "Movies", "type": "movies", "paths": ["/media/Movies"]}'

Webhooks

Add webhooks in Manage → Settings → Integrations, or in wrentv.yaml:

yamlwebhooks:
  - url: https://backend.example.com/wrentv
    secret: change-me
    events: [download.completed, download.failed]   # empty means all events

Events

EventSent when
download.completedA server download finished and the file is in its library
download.failedA server download failed (no space, quota reached, the stream stopped working)
stream.unavailableSomeone tried to play a streamed title and no cached release was found

Request

httpPOST https://backend.example.com/wrentv
Content-Type: application/json
X-WrenTV-Event: download.completed
X-WrenTV-Signature: sha256=<hex HMAC-SHA256 of the body, keyed with the webhook secret>
json{
  "event": "download.completed",
  "id": "…",
  "timestamp": 1760000000,
  "server": { "id": "…", "name": "Wren TV", "url": "https://wrentv.example.com" },
  "data": {
    "id": "…", "itemId": "…", "title": "Dune - Part Two (2024)", "quality": "2160p", "status": "done",
    "path": "/media/Movies/Dune - Part Two (2024) {tmdb-693134}/Dune - Part Two (2024) - 2160p.mkv",
    "bytes": 61234567890, "total": 61234567890
  }
}

stream.unavailable sends {"itemId", "title", "error"}. server.url uses the server's public_url, so set one if your backend runs elsewhere.

Verifying the signature

Compute the HMAC-SHA256 of the raw request body with your secret and compare it to the header in constant time:

jsimport { createHmac, timingSafeEqual } from 'node:crypto';

function verify(body, header, secret) {
  const want = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
  return header.length === want.length && timingSafeEqual(Buffer.from(header), Buffer.from(want));
}
Wren TV is in early access. Something here wrong or missing? Tell us in the beta.