Integrations
Conexiones¶
Desde la página Connections puedes crear y administrar webhooks basados en eventos y la ejecución de scripts.
Haz clic en New Connection para crear un nuevo webhook o una ejecución de script basada en eventos.
- Admite múltiples tipos de eventos, incluidos:
- Ciclo de vida del canal (start, stop, reconnect, error, failover)
- Operaciones de stream (switch),
- Eventos de grabación (start, end)
- Actualizaciones de datos (EPG, M3U)
- Actividad del cliente (connect, disconnect)
- Eventos de seguridad (login failed, EPG/M3U blocked)
- Reproducción de VOD (start, stop)
Los datos del evento están disponibles como variables de entorno en scripts (con el prefijo DISPATCHARR_), payloads POST para webhooks y payloads de ejecución de plugins.
Información
Los scripts deben colocarse en data/scripts y deben tener permisos de ejecución.
Ejemplo de script (clic para ver)
#!/usr/bin/env python3
import requests
import json
import os
import re
webhook_url = ""
message = ["Test event from Dispatcharr!"]
for key, value in os.environ.items():
# if not re.search(r"DISPATCHARR_", key):
# continue
key = key.replace("DISPATCHARR_", "").replace("_", " ").lower()
message.append(f"{key} = {value}")
# The data to be sent in the POST request
# 'content' is the key for a basic message.
# Please leave this thats how the project works!
data = {
"content": "\n".join(message)
}
# The headers to specify the content type
headers = {
"Content-Type": "application/json"
}
# Send the POST request
response = requests.post(webhook_url, data=json.dumps(data), headers=headers)
# Check if the request was successful
if response.status_code == 204:
print("Message sent successfully!")
else:
print(f"Failed to send message. Status code: {response.status_code}")
print(response.text)
Webhook Payload Templates¶
Cuando creas una conexión de webhook, opcionalmente puedes proporcionar un payload template. El template se renderiza con el lenguaje de templates de Django (el texto de ayuda menciona Jinja2, pero el motor es el de Django). Las variables del payload del evento se exponen directamente en el contexto del template, así que puedes referenciarlas como {{ variable_name }}.
Si el template renderizado se interpreta como JSON válido, Dispatcharr fuerza Content-Type: application/json en la solicitud saliente. De lo contrario, la cadena renderizada se envía por POST tal cual usando los headers que hayas configurado en la conexión.
Advertencia
Si dejas el template vacío, el dict crudo del payload del evento se pasa a requests.post(..., data=<dict>), lo que lo codifica como formulario application/x-www-form-urlencoded (por ejemplo channel_name=CNN&stream_name=...). No se envía como JSON. Si quieres JSON, proporciona un template. Incluso uno trivial como {{ payload|default:"" }} tampoco funcionará; debes renderizar el body tú mismo, por ejemplo {"channel": "{{ channel_name }}"}.
Variables auto-inyectadas (eventos con contexto de canal)¶
Cualquier evento que incluya un channel_id se enriquece en el momento del envío con los siguientes campos (tomados del registro del canal, del stream que se está reproduciendo y del M3U/profile en Redis):
| Variable | Description |
|---|---|
channel_name |
Nombre visible del canal |
stream_name |
Nombre del stream que se está reproduciendo actualmente |
stream_url |
URL upstream del stream que se está reproduciendo actualmente |
channel_url |
Alias de stream_url |
provider_name |
Nombre de la cuenta M3U que respalda el stream activo |
profile_used |
Nombre del StreamProfile aplicado al stream activo |
Los valores falsy se eliminan antes del render
Cualquier clave del payload cuyo valor sea falsy según la evaluación de Python, como None, "", 0, 0.0, False, [] vacío o {} vacío, se elimina del payload antes de renderizar el template. Eso significa que los campos numéricos y booleanos también pueden desaparecer: por ejemplo, bytes_written=0 en recording_end o interrupted=False no aparecerán con valor cero, sino que faltarán por completo. Si un campo podría faltar, protégelo con un valor por defecto: {{ stream_name|default:"-" }}. De lo contrario, una variable no definida se renderizará como una cadena vacía y puede producir JSON inválido.
Escapa las variables que van dentro de cadenas JSON
El motor de templates de Django hace escape HTML de <, >, &, ", ' por defecto, pero no escapa barras invertidas ni saltos de línea. Eso significa que:
- Una URL con parámetros de consulta
&renderizada como"url": "{{ stream_url }}"produce"url": "http://host/?a=1&b=2"; el JSON se interpreta bien, pero el receptor obtiene una URL con&literal. - Un
error_messageque contiene un salto de línea o una barra invertida (muy común en textos de excepciones) produce JSON inválido.
El patrón seguro para cualquier variable que termine dentro de una cadena JSON es el filtro escapejs, que escapa todo lo peligroso en un contexto de cadena JS/JSON (comillas, barras invertidas, saltos de línea, <, >, &) como secuencias \uXXXX. El resultado se ve ruidoso, pero es inequívoco después de JSON.parse:
Para destinos de texto plano como cuerpos de línea de ntfy, puedes usar |safe en cada variable para omitir el autoescape y evitar ruido tipo < en el mensaje renderizado.
Variables por evento¶
Las tablas a continuación muestran las variables que proporciona cada evento además de los campos auto-inyectados anteriores, cuando correspondan.
channel_start - Channel Started¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
stream_name |
Nombre del stream |
stream_id |
ID interno del stream |
channel_stop - Channel Stopped¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
runtime |
Tiempo total de ejecución en segundos |
total_bytes |
Total de bytes transmitidos |
channel_reconnect - Channel Reconnected¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
attempt |
Número actual del intento de reintento |
max_attempts |
Máximo de reintentos configurados |
channel_error - Channel Error¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
error_type |
Uno de connection_failed, connection_exception |
url |
Primeros 100 caracteres de la URL del stream |
attempts |
Cuántos intentos se hicieron antes de abandonar |
error_message |
Detalle de la excepción (solo en connection_exception) |
channel_failover - Channel Failover¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
reason |
Motivo por el que se activó el failover (por ejemplo buffering_timeout) |
duration |
Cuánto tiempo persistió la condición de fallo antes del failover |
stream_switch - Stream Switch¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
new_url |
Primeros 100 caracteres de la nueva URL del stream |
stream_id |
Nuevo ID del stream |
recording_start - Recording Started¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
recording_id |
ID de la fila de grabación |
recording_end - Recording Ended¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
recording_id |
ID de la fila de grabación |
interrupted |
True si la grabación terminó antes de tiempo |
bytes_written |
Total de bytes escritos en disco |
epg_refresh - EPG Refreshed¶
| Variable | Description |
|---|---|
source_name |
Nombre de la fuente EPG |
programs |
Número de programas ingeridos |
channels |
Número de canales con programas |
skipped_programs |
Programas omitidos durante el parseo |
unmapped_channels |
Canales EPG sin canal coincidente en Dispatcharr |
m3u_refresh - M3U Refreshed¶
| Variable | Description |
|---|---|
account_name |
Nombre de la cuenta M3U |
elapsed_time |
Duración de la actualización en segundos (redondeada a 2 decimales) |
streams_created |
Nuevos streams agregados en esta actualización |
streams_updated |
Streams existentes actualizados |
streams_deleted |
Streams eliminados porque el proveedor los quitó |
total_processed |
Total de streams vistos en la playlist |
client_connect - Client Connected¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
client_ip |
Dirección IP del cliente |
client_id |
ID interno del cliente por conexión |
user_agent |
User-Agent del cliente (truncado a 100 caracteres) |
username |
Nombre de usuario autenticado, si existe |
client_disconnect - Client Disconnected¶
| Variable | Description |
|---|---|
channel_name |
Nombre del canal |
client_ip |
Dirección IP del cliente |
client_id |
ID interno del cliente por conexión |
user_agent |
User-Agent del cliente (truncado a 100 caracteres) |
duration |
Duración de la sesión en segundos (redondeada a 2 decimales) |
bytes_sent |
Bytes enviados a este cliente |
username |
Nombre de usuario autenticado, si existe |
login_failed - Login Failed¶
| Variable | Description |
|---|---|
user |
Nombre de usuario enviado (o unknown / token_refresh) |
client_ip |
Dirección IP del cliente |
user_agent |
User-Agent del cliente |
reason |
Motivo del fallo (credenciales inválidas, acceso de red denegado, etc.) |
epg_blocked - EPG Blocked¶
| Variable | Description |
|---|---|
profile |
Nombre del output profile (o all) |
reason |
Motivo por el que se bloqueó la solicitud |
client_ip |
Dirección IP del cliente |
user_agent |
User-Agent del cliente |
m3u_blocked - M3U Blocked¶
| Variable | Description |
|---|---|
profile |
Nombre del output profile (o all) presente en la ruta M3U estándar |
user |
Nombre de usuario enviado presente en la ruta de la API Xtream Codes |
reason |
Motivo por el que se bloqueó la solicitud |
client_ip |
Dirección IP del cliente |
user_agent |
User-Agent del cliente |
vod_start / vod_stop - VOD Started / VOD Stopped¶
| Variable | Description |
|---|---|
content_name |
Título del VOD |
content_uuid |
UUID del contenido VOD (compartido por el par start/stop correspondiente) |
client_ip |
Dirección IP del cliente |
username |
Nombre de usuario autenticado, si existe |
Cosas a tener en cuenta¶
- Los valores falsy desaparecen. Como se indicó arriba, Dispatcharr elimina cualquier clave del payload cuyo valor esté vacío antes de renderizar. Usa siempre
{{ var|default:"-" }}o rodea los campos con{% if var %}...{% endif %}para mantener tu JSON válido. - Los campos de contexto de canal pueden faltar en algunos eventos.
stream_name,stream_url,channel_url,provider_nameyprofile_usedse llenan a partir de una filaStream. El dispatcher usa unstream_idexplícito desde el sitio de llamada si se pasó uno, lo que ocurre enchannel_startystream_switch; de lo contrario, usa la clave de Redischannel_stream:<channel.id>. Si ninguno produce un stream, los cinco campos seránNoney se eliminarán antes del render. En la práctica, esto afecta eventos comochannel_stop,channel_reconnect,channel_error,channel_failover,recording_start,recording_end,client_connectyclient_disconnectcuando el estado del stream en vivo en Redis ya se limpió. - Algunos eventos registrados no disparan webhooks.
login_success,logout,m3u_download,epg_downloadychannel_bufferingse escriben en el log de System Events, pero no están en el registro de eventos de webhook, así que las suscripciones a esos eventos nunca se ejecutarán. - El Channel UUID no se expone a los templates. La capa de envío acepta internamente un argumento
channel_idy lo usa como clave de búsqueda UUID para la filaChannel, pero ese valor se consume en ese punto y no se agrega al contexto del template.{{ channel_id }}en un payload template se renderiza como una cadena vacía. Usa{{ channel_name }}para identificar el canal. - No hay firma HMAC integrada. Si tu receptor necesita autenticar el webhook, coloca un secreto compartido largo en los headers de la conexión, por ejemplo
X-Auth-Token, y valídalo en el extremo receptor.
Ejemplos prácticos¶
Discord - channel start / stop¶
Usa dos suscripciones separadas con etiquetas codificadas de forma fija. No intentes detectar start vs. stop a partir de la presencia de total_bytes o runtime, porque una sesión legítima de 0 bytes se eliminaría y se clasificaría mal.
Para channel_start:
{
"username": "Dispatcharr",
"embeds": [{
"title": "{{ channel_name|default:"Unknown"|escapejs }} started",
"fields": [
{"name": "Stream", "value": "{{ stream_name|default:"-"|escapejs }}", "inline": true},
{"name": "Provider", "value": "{{ provider_name|default:"-"|escapejs }}", "inline": true},
{"name": "Profile", "value": "{{ profile_used|default:"-"|escapejs }}", "inline": true}
]
}]
}
Para channel_stop:
{
"username": "Dispatcharr",
"embeds": [{
"title": "{{ channel_name|default:"Unknown"|escapejs }} stopped",
"fields": [
{"name": "Stream", "value": "{{ stream_name|default:"-"|escapejs }}", "inline": true},
{"name": "Provider", "value": "{{ provider_name|default:"-"|escapejs }}", "inline": true},
{"name": "Runtime (s)", "value": "{{ runtime|default:0 }}", "inline": true},
{"name": "Bytes", "value": "{{ total_bytes|default:0 }}", "inline": true}
]
}]
}
Slack - error / failover / reconnect alerts¶
Un solo webhook de Slack cubre los tres eventos de tipo error. Observa el filtro |escapejs en cada valor de variable. Sin él, un error_message con un salto de línea o una barra invertida, algo muy común en texto crudo de excepciones de Python, producirá JSON inválido y fallará la entrega:
{
"text": ":warning: *{{ channel_name|escapejs }}* - {% if error_type %}{{ error_type|escapejs }}{% elif reason %}{{ reason|escapejs }}{% else %}reconnect {{ attempt }}/{{ max_attempts }}{% endif %}",
"attachments": [{
"color": "warning",
"fields": [
{"title": "Provider", "value": "{{ provider_name|default:"-"|escapejs }}", "short": true},
{"title": "Stream", "value": "{{ stream_name|default:"-"|escapejs }}", "short": true}
{% if error_message %},{"title": "Detail", "value": "{{ error_message|escapejs }}", "short": false}{% endif %}
]
}]
}
ntfy / Gotify / Pushover - notificaciones móviles para eventos de seguridad¶
Suscribe login_failed, m3u_blocked y epg_blocked a un topic de ntfy. ntfy acepta un body de texto plano, así que el template no necesita ser JSON:
El propio nombre del evento no está en el contexto del template, solo el dict del payload, así que inclúyelo en los headers de la conexión junto con otros metadatos de ntfy:
Home Assistant - estado now-playing¶
Usa dos suscripciones separadas con valores state codificados de forma fija, por el mismo motivo que en Discord. Observa |escapejs en stream_url; sin él, una URL con parámetros & como casi cualquier URL Xtream o HLS se renderizaría como & y Home Assistant almacenaría una URL rota.
Para channel_start y stream_switch:
{
"state": "playing",
"channel": "{{ channel_name|escapejs }}",
"stream": "{{ stream_name|escapejs }}",
"provider": "{{ provider_name|escapejs }}",
"url": "{{ stream_url|escapejs }}"
}
Para channel_stop:
Controla un input_text o un template sensor desde la automatización de webhook del lado de Home Assistant.
InfluxDB - métricas de salud del proveedor¶
Envía m3u_refresh y epg_refresh a un endpoint HTTP write de InfluxDB usando line protocol de InfluxDB. Es texto plano, no JSON, así que no se activará la detección de JSON. Los dos eventos no comparten nombres de campo, así que usa una suscripción con un template para cada uno.
Advertencia
InfluxDB line protocol requiere que los valores de tag escapen espacios, comas y =. Django no tiene un filtro incorporado para line protocol, así que la solución rápida es |cut:" "|cut:","|cut:"=" para quitar esos caracteres del tag. La alternativa más limpia es renombrar tus cuentas M3U o tus fuentes EPG para que usen solo caracteres alfanuméricos y guion.
Para m3u_refresh:
m3u_refresh,account={{ account_name|cut:" "|cut:","|cut:"=" }} created={{ streams_created|default:0 }},updated={{ streams_updated|default:0 }},deleted={{ streams_deleted|default:0 }},elapsed={{ elapsed_time|default:0 }}
Para epg_refresh:
epg_refresh,source={{ source_name|cut:" "|cut:","|cut:"=" }} programs={{ programs|default:0 }},channels={{ channels|default:0 }},skipped={{ skipped_programs|default:0 }},unmapped={{ unmapped_channels|default:0 }}
Logs¶
Las conexiones activadas, sus respuestas y cualquier error se registrarán aquí.