Enviado por admin el
Asterisk 20.10.0 introdujo una característica largamente esperada por la comunidad: la aplicación de dialplan PJSIPNotify. Esta nueva funcionalidad permite enviar mensajes SIP NOTIFY directamente desde el dialplan, eliminando la necesidad de depender exclusivamente del CLI o AMI para esta tarea.
En este artículo exploraremos en profundidad esta nueva capacidad, sus casos de uso prácticos, y cómo implementarla en tus sistemas de producción.
¿Qué es SIP NOTIFY?
Fundamentos del Protocolo
SIP NOTIFY es un método definido en RFC 3265 que permite a un servidor o endpoint SIP informar a suscriptores sobre cambios de estado o eventos específicos. Es el complemento del método SUBSCRIBE: un dispositivo se suscribe a un evento, y el servidor envía NOTIFY cuando ese evento ocurre.
Flujo básico SUBSCRIBE/NOTIFY:
Teléfono SIP Servidor PBX
| |
|----SUBSCRIBE MWI-------->|
|<-------200 OK------------|
|<------NOTIFY (0 msgs)----|
|-------200 OK------------>|
| |
[Llega un mensaje de voz]
| |
|<------NOTIFY (1 msg)-----|
|-------200 OK------------>|
| [Luz MWI se enciende] |
Casos de Uso Comunes
Los mensajes NOTIFY se utilizan ampliamente para:
- Message Waiting Indicator (MWI): Indicador luminoso de mensajes de voz
- Busy Lamp Field (BLF): Estado de otras extensiones (ocupado/libre/sonando)
- Presence: Estado de disponibilidad de usuarios
- Call Pickup: Notificación de llamadas disponibles para captura
- Auto-provisioning: Solicitud de reconfiguration del dispositivo
- Custom Events: Eventos personalizados específicos de aplicación
La Limitación Histórica
Antes de Asterisk 20.10.0, enviar mensajes NOTIFY manualmente requería:
Opción 1: Desde CLI
asterisk -rx "pjsip send notify check-voicemail endpoint 1001"
Opción 2: Desde AMI (Asterisk Manager Interface)
Action: PJSIPNotify
Endpoint: 1001
Variable: Event=check-voicemail
Problemas con estos métodos:
- No se pueden enviar desde el dialplan durante una llamada activa
- Requieren acceso CLI o AMI (problemas de seguridad)
- Difícil integración en lógica de llamadas
- No se pueden enviar dentro del diálogo SIP actual
PJSIPNotify: La Nueva Aplicación
Sintaxis
PJSIPNotify([to],content)
Parámetros:
-
to (opcional): URI SIP arbitraria a la que enviar el NOTIFY
- Si se omite, envía dentro del diálogo SIP actual del canal
- Formato:
<sip:usuario@dominio:puerto>
-
content: Contenido del mensaje
- Puede ser una entrada pre-configurada en
pjsip_notify.conf - O una lista de headers especificados en dialplan con formato
&Header=Valor
- Puede ser una entrada pre-configurada en
Requisito Importante
Para enviar NOTIFY a URIs arbitrarias (fuera del diálogo actual), debes configurar default_outbound_endpoint en la sección [global] de pjsip.conf:
[global]
default_outbound_endpoint=default-outbound
[default-outbound]
type=endpoint
context=from-internal
disallow=all
allow=ulaw
allow=alaw
Este endpoint determina el contacto del mensaje NOTIFY saliente.
Ejemplos Prácticos
Ejemplo 1: NOTIFY Dentro del Diálogo Actual
Escenario: Enviar un evento personalizado durante una llamada activa.
[test-notify]
exten => 7000,1,Answer()
same => n,Playback(hello-world)
same => n,PJSIPNotify(,&Event=Test&X-Data=Fun)
same => n,Wait(2)
same => n,Hangup()
Resultado SIP:
NOTIFY sip:1001@192.168.1.10:5060 SIP/2.0
Event: Test
X-Data: Fun
Content-Length: 0
Ejemplo 2: NOTIFY con Configuración Pre-definida
Paso 1: Configurar el template en pjsip_notify.conf
[potato-notify]
Event=>PotatoReport
Content-type=>application/potato-summary
Content=>Num-Potatoes: 23
Content=>Potato-Quality: fair
Content=>
Paso 2: Usar desde dialplan
[potato-report]
exten => 7001,1,Answer()
same => n,PJSIPNotify(,potato-notify)
same => n,Hangup()
Resultado SIP:
NOTIFY sip:1001@192.168.1.10:5060 SIP/2.0
Event: PotatoReport
Content-Type: application/potato-summary
Content-Length: 38
Potato-Quality: fair
Num-Potatoes: 23
Ejemplo 3: NOTIFY a URI Arbitraria
Escenario: Enviar notificación a un endpoint específico desde el dialplan.
[notify-endpoint]
exten => 7002,1,Answer()
same => n,Set(TARGET_URI=<sip:bob@127.0.0.1:5260>)
same => n,PJSIPNotify(${TARGET_URI},&Event=Custom&X-Message=Hello Bob)
same => n,Playback(notification-sent)
same => n,Hangup()
Ejemplo 4: Message Waiting Indicator (MWI)
Configuración en pjsip_notify.conf:
[mwi-update]
Event=>message-summary
Content-type=>application/simple-message-summary
Content=>Messages-Waiting: yes
Content=>Voice-Message: 2/0 (0/0)
Content=>
Dialplan:
[update-mwi]
exten => 8000,1,Answer()
same => n,Verbose(1,Updating MWI for extension ${EXTEN})
same => n,PJSIPNotify(,mwi-update)
same => n,Playback(mwi-updated)
same => n,Hangup()
Ejemplo 5: Forced Answer (Auto-Answer)
Algunos teléfonos SIP soportan auto-answer mediante NOTIFY con headers especiales.
pjsip_notify.conf:
[force-answer]
Event=>talk
Content-type=>application/x-broadworks-callinfo
Content=>call-info: ;answer-after=0
Content=>
Dialplan - Intercomunicador:
[intercom]
exten => _*80X.,1,NoOp(Intercom to ${EXTEN:3})
same => n,Set(TARGET_EXTEN=${EXTEN:3})
same => n,Set(TARGET_URI=<sip:${TARGET_EXTEN}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${TARGET_URI},force-answer)
same => n,Wait(1)
same => n,Dial(PJSIP/${TARGET_EXTEN},30,A(beep))
same => n,Hangup()
Uso: Marcar *80 + extensión para activar auto-answer en el teléfono destino.
Casos de Uso Avanzados
Caso 1: Sistema de Paging con Auto-Answer
Objetivo: Crear un sistema de paging donde múltiples teléfonos responden automáticamente.
pjsip_notify.conf:
[paging-alert]
Event=>talk
Content-type=>application/x-broadworks-callinfo
Content=>call-info: ;answer-after=0;priority=emergency
Content=>
extensions.conf:
[paging-system]
; Paging a grupo de extensiones
exten => *70,1,Answer()
same => n,Set(PAGING_GROUP=1001&1002&1003&1004)
same => n,Playback(paging-announcement)
same => n,Set(i=1)
same => n,While($[${i} <= ${FIELDQTY(PAGING_GROUP,&)}])
same => n,Set(EXTEN=${CUT(PAGING_GROUP,&,${i})})
same => n,Set(URI=<sip:${EXTEN}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${URI},paging-alert)
same => n,Set(i=$[${i} + 1])
same => n,EndWhile
same => n,Wait(2)
same => n,Page(${PAGING_GROUP},,A(beep))
same => n,Hangup()
Caso 2: Actualización Dinámica de BLF
Objetivo: Actualizar el estado BLF de una extensión cuando cambia su estado de disponibilidad.
pjsip_notify.conf:
[blf-busy]
Event=>dialog
Content-type=>application/dialog-info+xml
Content=><?xml version="1.0"?>
Content=><dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info"
Content=> version="1" state="full" entity="sip:${EXTEN}@${SIPDOMAIN}">
Content=> <dialog id="1">
Content=> <state>confirmed</state>
Content=> </dialog>
Content=></dialog-info>
Content=>
[blf-free]
Event=>dialog
Content-type=>application/dialog-info+xml
Content=><?xml version="1.0"?>
Content=><dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info"
Content=> version="1" state="full" entity="sip:${EXTEN}@${SIPDOMAIN}">
Content=> <dialog id="1">
Content=> <state>terminated</state>
Content=> </dialog>
Content=></dialog-info>
Content=>
extensions.conf:
[blf-control]
; Marcar *71 para aparecer ocupado
exten => *71,1,Answer()
same => n,Set(DEVICE_STATE(Custom:DND${CALLERID(num)})=INUSE)
same => n,PJSIPNotify(,blf-busy)
same => n,Playback(do-not-disturb&activated)
same => n,Hangup()
; Marcar *70 para aparecer libre
exten => *70,1,Answer()
same => n,Set(DEVICE_STATE(Custom:DND${CALLERID(num)})=NOT_INUSE)
same => n,PJSIPNotify(,blf-free)
same => n,Playback(do-not-disturb&de-activated)
same => n,Hangup()
Caso 3: Rechazo Automático de Llamadas (Call Rejection)
Objetivo: Enviar NOTIFY para indicar al teléfono que rechace una llamada entrante automáticamente.
pjsip_notify.conf:
[reject-call]
Event=>call-completion
Content-type=>application/x-call-rejection
Content=>Reject-Reason: busy-everywhere
Content=>
extensions.conf con Lista Negra:
[from-trunk]
exten => _X.,1,NoOp(Incoming call from ${CALLERID(num)})
same => n,GotoIf($[${BLACKLIST()}=1]?blacklisted:accept)
same => n(blacklisted),Set(CALLER_URI=<sip:${CALLERID(num)}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${CALLER_URI},reject-call)
same => n,Busy(20)
same => n,Hangup()
same => n(accept),Dial(PJSIP/${EXTEN})
same => n,Hangup()
Caso 4: Provisioning Remoto de Teléfonos
Objetivo: Forzar reconfiguration de teléfonos desde el dialplan.
pjsip_notify.conf:
[polycom-check-cfg]
Event=>check-sync
Content-Length=>0
[yealink-autoprov]
Event=>check-sync;reboot=false
Content-Length=>0
[cisco-resync]
Event=>check-sync
Content-type=>application/url
Content=>http://provisioning.example.com/cfg/${MAC}.xml
Content=>
extensions.conf:
[admin-tools]
; Reconfigurar teléfono Polycom
exten => *90XXX,1,Answer()
same => n,Set(EXTEN_NUM=${EXTEN:3})
same => n,Set(TARGET_URI=<sip:${EXTEN_NUM}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${TARGET_URI},polycom-check-cfg)
same => n,Playback(configuration-updated)
same => n,Hangup()
; Reconfigurar todos los teléfonos de un tipo
exten => *91,1,Answer()
same => n,Playback(please-wait)
same => n,System(/usr/local/bin/bulk-reprov.sh polycom)
same => n,Playback(completed)
same => n,Hangup()
Script bulk-reprov.sh:
#!/bin/bash
PHONE_TYPE=$1
case $PHONE_TYPE in
polycom)
NOTIFY_CFG="polycom-check-cfg"
;;
yealink)
NOTIFY_CFG="yealink-autoprov"
;;
cisco)
NOTIFY_CFG="cisco-resync"
;;
esac
# Obtener lista de endpoints del tipo especificado
asterisk -rx "pjsip show endpoints" | grep "$PHONE_TYPE" | while read line; do
ENDPOINT=$(echo $line | awk '{print $1}')
asterisk -rx "pjsip send notify $NOTIFY_CFG endpoint $ENDPOINT"
sleep 1
done
Mejora del CLI: pjsip send notify channel
Junto con PJSIPNotify, se mejoró el comando CLI para soportar envío a canales específicos:
Nueva sintaxis:
pjsip send notify <option> channel <channel>
Ejemplo:
# Enviar a endpoint (anterior)
pjsip send notify check-voicemail endpoint 1001
# NUEVO: Enviar a canal específico
pjsip send notify check-voicemail channel PJSIP/1001-00000001
Esto permite enviar NOTIFY dentro del contexto de una llamada activa desde CLI.
Debugging y Troubleshooting
Habilitar Logging SIP
Para ver los mensajes NOTIFY en los logs:
# CLI
asterisk -rvvvv
pjsip set logger on
O en logger.conf:
[logfiles]
full => notice,warning,error,debug,verbose
messages => notice,warning,error
Captura de Paquetes
Usar sngrep para visualizar el flujo SIP:
sngrep port 5060
Filtra por NOTIFY:
F7 > Method: NOTIFY
Verificar Configuración
# Ver configuración de notify
asterisk -rx "pjsip show notify"
# Verificar endpoint default
asterisk -rx "pjsip show endpoint default-outbound"
# Test de envío
asterisk -rx "pjsip send notify test-notify endpoint 1001"
Problemas Comunes
Problema 1: "No default outbound endpoint configured"
WARNING: No default outbound endpoint configured
Solución: Configurar en pjsip.conf:
[global]
default_outbound_endpoint=default-outbound
Problema 2: Dispositivo no responde a NOTIFY
Verificar que el dispositivo soporte el Event específico:
# Capturar SUBSCRIBE del dispositivo para ver qué eventos solicita
sngrep
# Filtrar por SUBSCRIBE y revisar header Event:
Problema 3: NOTIFY enviado pero sin efecto
Algunos dispositivos requieren headers específicos:
[custom-notify]
Event=>talk
Content-type=>application/x-broadworks-callinfo
Content=>call-info: ;answer-after=0
; Header específico del fabricante:
X-Vendor-Specific=>custom-value
Content=>
Integración con AMI
Aunque PJSIPNotify es para dialplan, puedes combinarlo con AMI para control externo:
Ejemplo en Python:
import asterisk.manager
def send_notify_via_dialplan(extension, notify_config):
"""Envía NOTIFY ejecutando contexto de dialplan vía AMI"""
manager = asterisk.manager.Manager()
manager.connect('localhost')
manager.login('admin', 'secret')
# Originar llamada que ejecuta PJSIPNotify
response = manager.originate(
channel=f'Local/{extension}@send-notify',
context='send-notify-handler',
exten='s',
priority=1,
variables={
'NOTIFY_CONFIG': notify_config,
'TARGET_EXTEN': extension
}
)
manager.logoff()
return response
Dialplan correspondiente:
[send-notify]
exten => _X.,1,NoOp(Trigger notify for ${EXTEN})
same => n,Return()
[send-notify-handler]
exten => s,1,NoOp(Sending ${NOTIFY_CONFIG} to ${TARGET_EXTEN})
same => n,Set(URI=<sip:${TARGET_EXTEN}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${URI},${NOTIFY_CONFIG})
same => n,Hangup()
Comparación: Antes vs Después
Antes de PJSIPNotify
Escenario: Enviar MWI update después de procesar voicemail.
[voicemail-handler]
exten => s,1,VoiceMail(${EXTEN}@default,u)
same => n,System(echo "pjsip send notify mwi-update endpoint ${EXTEN}" | asterisk -rx)
same => n,Hangup()
Problemas:
- Llamada externa a shell
- Performance overhead
- Difícil de debuggear
- Posibles race conditions
Después de PJSIPNotify
[voicemail-handler]
exten => s,1,VoiceMail(${EXTEN}@default,u)
same => n,PJSIPNotify(,mwi-update)
same => n,Hangup()
Ventajas:
- Nativo del dialplan
- Mejor performance
- Dentro del diálogo SIP actual
- Logging integrado
Best Practices
1. Usar Configuraciones Pre-definidas
En lugar de:
same => n,PJSIPNotify(,&Event=check-sync&Content-type=text/plain&Content=update)
Mejor:
; pjsip_notify.conf
[device-update]
Event=>check-sync
Content-type=>text/plain
Content=>update
Content=>
; extensions.conf
same => n,PJSIPNotify(,device-update)
Razón: Más legible, reutilizable, fácil de mantener.
2. Validar URIs Dinámicas
[safe-notify]
exten => 8000,1,NoOp(Notify to ${TARGET})
same => n,GotoIf($[${LEN(${TARGET})} = 0]?error)
same => n,GotoIf($["${TARGET}" = ""]?error)
same => n,Set(URI=<sip:${TARGET}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${URI},custom-notify)
same => n,Hangup()
same => n(error),Playback(invalid)
same => n,Hangup()
3. Logging Apropiado
same => n,Verbose(1,Sending ${NOTIFY_TYPE} NOTIFY to ${TARGET_URI})
same => n,PJSIPNotify(${TARGET_URI},${NOTIFY_TYPE})
same => n,NoOp(PJSIPNOTIFY result: ${PJSIPNOTIFYSTATUS})
4. Manejo de Errores
same => n,PJSIPNotify(${URI},check-sync)
same => n,GotoIf($["${PJSIPNOTIFYSTATUS}" = "SUCCESS"]?ok:failed)
same => n(failed),Log(ERROR,Failed to send NOTIFY to ${URI})
same => n,Goto(cleanup)
same => n(ok),Verbose(1,NOTIFY sent successfully)
same => n(cleanup),Hangup()
5. Rate Limiting
Si envías NOTIFY en bucle, implementa rate limiting:
[bulk-notify]
exten => 9000,1,Answer()
same => n,Set(EXTENSIONS=1001&1002&1003&1004&1005)
same => n,Set(i=1)
same => n,While($[${i} <= ${FIELDQTY(EXTENSIONS,&)}])
same => n,Set(EXT=${CUT(EXTENSIONS,&,${i})})
same => n,Set(URI=<sip:${EXT}@${SIPDOMAIN}>)
same => n,PJSIPNotify(${URI},update-config)
same => n,Wait(0.5) ; Rate limiting: 500ms entre mensajes
same => n,Set(i=$[${i} + 1])
same => n,EndWhile
same => n,Hangup()
Consideraciones de Seguridad
1. Validación de Input
[user-notify]
; MAL - Vulnerable a injection
exten => 7000,1,Set(MSG=${CALLERID(name)})
same => n,PJSIPNotify(,&Event=UserMessage&X-Data=${MSG})
; BIEN - Sanitizado
exten => 7000,1,Set(MSG=${FILTER(A-Za-z0-9 ,${CALLERID(name)})})
same => n,GotoIf($[${LEN(${MSG})} > 50]?truncate)
same => n,Goto(send)
same => n(truncate),Set(MSG=${MSG:0:50})
same => n(send),PJSIPNotify(,&Event=UserMessage&X-Data=${MSG})
2. Restricción de Destinos
[notify-restricted]
exten => _90XX,1,NoOp(Admin notify to ${EXTEN:2})
same => n,GotoIf($["${CALLERID(num)}" != "1000"]?unauthorized)
same => n,Set(TARGET=${EXTEN:2})
same => n,PJSIPNotify(<sip:${TARGET}@${SIPDOMAIN}>,admin-msg)
same => n,Hangup()
same => n(unauthorized),Playback(access-denied)
same => n,Hangup()
3. Audit Logging
[logged-notify]
exten => 8000,1,NoOp(=== NOTIFY Request ===)
same => n,Set(CDR(userfield)=NOTIFY:${NOTIFY_TYPE}:${TARGET})
same => n,Log(NOTICE,User ${CALLERID(num)} sending ${NOTIFY_TYPE} to ${TARGET})
same => n,PJSIPNotify(${TARGET_URI},${NOTIFY_TYPE})
same => n,Hangup()
Conclusión
PJSIPNotify en Asterisk 20+ representa una mejora significativa en el control programático de mensajes SIP NOTIFY. Las principales ventajas son:
Ventajas Técnicas:
- Control nativo desde dialplan
- Sin overhead de llamadas externas
- Integración seamless en lógica de llamadas
- Mejor performance y debugging
Casos de Uso Habilitados:
- MWI dinámico durante llamadas
- Auto-answer/paging systems
- Provisioning remoto automático
- Custom event notifications
- Integración con aplicaciones externas
Mejores Prácticas:
- Usar configuraciones pre-definidas en pjsip_notify.conf
- Implementar validación y sanitización de inputs
- Logging apropiado para auditoría
- Rate limiting en operaciones bulk
- Manejo de errores robusto
La combinación de PJSIPNotify con las capacidades existentes de Asterisk abre nuevas posibilidades para integración de telefonía y aplicaciones, especialmente en escenarios donde se requiere control fino del comportamiento de endpoints durante llamadas activas.
Referencias
- RFC 3265 - SIP Event Notification
- Asterisk PJSIPNotify Documentation
- Asterisk 20.10.0 Release Notes
- Blog Post: Introducing PJSIPNotify
¿Has implementado PJSIPNotify en tu sistema? Comparte tu experiencia en los comentarios.
Comentarios recientes