Webhooks
ChannelWatch can send notifications to your own HTTP endpoint as outbound webhooks. This is a separate notification surface from Custom (Apprise) and Provider Plugins. Use webhooks when you want ChannelWatch to POST a signed JSON payload to another system you control.
What webhooks are
Section titled “What webhooks are”Each enabled webhook sends an HTTP POST request to the URL you configure. ChannelWatch includes a JSON body, an event type, a delivery ID, and an HMAC signature header so your receiver can verify that the request came from a sender that knows the shared secret.
ChannelWatch sends webhooks only when a webhook entry is enabled and has both a URL and a secret. If the secret is missing, delivery is skipped.
How ChannelWatch signs webhook requests
Section titled “How ChannelWatch signs webhook requests”ChannelWatch signs the raw request body with HMAC-SHA256 and sends the result in the X-ChannelWatch-Signature header.
- Algorithm:
HMAC-SHA256 - Signature header:
X-ChannelWatch-Signature - Format:
sha256=<hex-digest>
Two additional headers are included on every request:
X-ChannelWatch-Delivery: the notification payload’s delivery ID, reused across its endpoint deliveries and retriesX-ChannelWatch-Event: the event type, such astestorrecording.started
Configuring a webhook endpoint
Section titled “Configuring a webhook endpoint”In ChannelWatch, each webhook entry contains three fields:
url: the destination endpointsecret: the shared secret used for HMAC signingenabled: whether ChannelWatch should send to that endpoint
- Open Settings > Notifications > Outbound Webhooks. On mobile, select Show webhook settings.
- Select Add Webhook, enter the Destination URL and a private Signing Secret, and enable the entry.
- For a local endpoint, review the exact scheme, host, and port before approving Trust this destination. This approval is limited to notification delivery; it does not allow private image fetching.
- Save, then check Settings > Routing for the DVR and alert family.

Your receiving service should:
- Accept
POSTrequests with a JSON body - Read the
X-ChannelWatch-Signatureheader - Recompute the HMAC with the same shared secret
- Compare the computed value with the received signature using a constant-time comparison
- Return a
2xxresponse when the payload is accepted
Treat X-ChannelWatch-Delivery as an idempotency key. The same payload can arrive more than once, so record accepted IDs and avoid repeating destructive downstream actions. HMAC verifies the body against the shared secret; it does not itself prevent replay. Apply any timestamp tolerance and replay policy at your receiver.
If your receiver depends on links back to the ChannelWatch instance, set one of these environment variables on the ChannelWatch side so instanceUrl in the payload uses the correct public URL:
CHANNELWATCH_INSTANCE_URLCW_INSTANCE_URLAPP_URL
If none of those variables are set, ChannelWatch uses http://localhost:8501 as the fallback instanceUrl value.
Verifying the HMAC signature
Section titled “Verifying the HMAC signature”The verification flow is the same in every language:
- Read the raw request body as bytes
- Compute
HMAC-SHA256(secret, body) - Prefix the hex digest with
sha256= - Compare it to
X-ChannelWatch-Signature
Example: Node.js
Section titled “Example: Node.js”import crypto from 'node:crypto';
function verifyChannelWatchSignature(rawBody, signatureHeader, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = Buffer.from(signatureHeader || '', 'utf8'); const computed = Buffer.from(expected, 'utf8');
if (received.length !== computed.length) { return false; }
return crypto.timingSafeEqual(received, computed);}Example: Python
Section titled “Example: Python”import hashlibimport hmac
def verify_channelwatch_signature(raw_body: bytes, signature_header: str, secret: str) -> bool: expected = "sha256=" + hmac.new( secret.encode("utf-8"), raw_body, hashlib.sha256, ).hexdigest()
return hmac.compare_digest(signature_header or "", expected)Payload format
Section titled “Payload format”ChannelWatch sends a JSON object with this shape. This is a synthetic example, not a captured delivery:
{ "eventType": "notification", "timestamp": "2026-04-21T00:00:00Z", "instanceName": "ChannelWatch", "instanceUrl": "http://localhost:8501", "version": "1.6.5", "deliveryId": "550e8400-e29b-41d4-a716-446655440000", "dvr_id": "dvr_1234", "dvr_name": "Living Room", "data": { "title": "Test title", "message": "Test message", "imageUrl": null }}Field meanings:
eventType: the normalized event name chosen by ChannelWatchtimestamp: when the payload was generated, in UTC ISO 8601 formatinstanceName: the ChannelWatch app nameinstanceUrl: the configured instance URL, orhttp://localhost:8501if no instance URL env var is setversion: the ChannelWatch version stringdeliveryId: a unique ID for this notification payload, reused across its endpoints and retriesdvr_id: the DVR ID tied to the alert, when availabledvr_name: the DVR display name tied to the alert, when availabledata.title: the notification titledata.message: the notification message bodydata.imageUrl: an image URL when present, otherwisenull
Current webhook event types include these values:
testchannel.watching.startvod.playback.startdisk.space.warningdisk.space.criticalrecording.scheduledrecording.startedrecording.cancelledrecording.completedrecording.updatedalert.notificationnotification
These event names are derived from the notification title and message in v1.6.0, rather than being a complete raw DVR event schema. Custom templates can affect classification. Do not assume every recording outcome has a dedicated webhook event name; keep the title and message when you need that detail.
Troubleshooting
Section titled “Troubleshooting”Signature verification fails
Section titled “Signature verification fails”Check these first:
- Make sure your receiver uses the raw request body bytes, not parsed JSON that has been serialized again.
- Verify that your shared secret exactly matches the secret configured in ChannelWatch.
- Confirm that you’re reading
X-ChannelWatch-Signature, notX-ChannelWatch-DeliveryorX-ChannelWatch-Event. - Make sure your comparison includes the
sha256=prefix.
If you rotate the webhook secret in ChannelWatch, update the receiving service at the same time. Old signatures will stop matching immediately.
No POST received
Section titled “No POST received”ChannelWatch attempts each webhook up to 3 times. Each request uses a 5 second timeout, with exponential retry delays of 1 second and 2 seconds between retries.
If your endpoint still receives nothing:
- Confirm the webhook entry is enabled.
- Confirm the destination URL is correct and reachable from the ChannelWatch container.
- Confirm a secret is configured. ChannelWatch skips delivery when the secret is missing.
- If you deliberately send a real test, use the v1.6.0 test guidance, then review the Delivery Log, receiver, and container logs for timeout or HTTP status errors.
With multiple endpoints, the shared webhook result is successful if at least one endpoint accepts the payload. A sent row therefore does not confirm every endpoint. Check each receiver when verifying a multi-endpoint setup.
Related pages
Section titled “Related pages”- Custom (Apprise) for third-party notification services that already have an Apprise URL scheme
- Delivery Log to inspect webhook attempts and failures
- Provider Plugins if you need custom Python delivery logic instead of an HTTP endpoint