Example local policy scripts
Here are some example local policy scripts that illustrate how to structure a script, manipulate variables and format the data responses.
You can use and adapt these scripts as appropriate for your own environment. See Using filters in local policy scripts for more information about the filters used in these examples.
This is the smallest "no changes" service configuration script that you can use. It will allow a call to continue if it was going to be allowed anyway — and reject it if it was going to be rejected.
We recommend that you use this as the basis for your own scripts and add extra logic to get your desired functionality.
{
{% if service_config %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This is the smallest "no changes" media location script that you can use. It simply returns the original set of suggested_media_overflow_locations without modification.
This script extends the minimum "no impact" service configuration script above by adding a {{pex_debug_log("Call information ", call_info) }} line.
When this script runs it will not change any configuration data but it will record the contents of the call_info variable — all of the information relating to the call being processed — to the support log. This allows you to analyze the nature of the call information for different call types and help you construct your script appropriately. To see the call information data go to and then search for entries that contain pex_debug_log.
To avoid filling the support log and causing it to rotate, remove all pex_debug_log filters from your scripts as soon as they are working correctly.
{
{{pex_debug_log("Call information ", call_info) }}
{% if service_config %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This media location script sets the location to be used for media to "Oslo", and sets "London" and "New York" as the primary and secondary overflow locations if "Oslo" is out of capacity. These locations will be used for every call / media location request, regardless of where call signaling is received.
{
"result" : {
"location" : "Oslo",
"primary_overflow_location" : "London",
"secondary_overflow_location" : "New York"
}
}
This media location script explicitly sets the location to use for media to "DMZ" if the call being made is an outbound RTMP call (and also overrides any original primary and secondary overflow locations). All other call types will continue to use the original media locations.
{
{% if
call_info.protocol == "rtmp" and
call_info.call_direction == "dial_out"
%}
"result" : {
"location" : "DMZ",
"primary_overflow_location" : "Location B",
"secondary_overflow_location" : "Location C"
}
{% else %}
"result" : {{suggested_media_overflow_locations|pex_to_json}}
{% endif %}
}
This service configuration script removes the PIN for participants in "Location A" or "Location C" (these might, for example, be "internal" locations) but keeps any existing PIN requirements for participants in other locations.
{
{% if service_config %}
"action" : "continue",
{% if call_info.location == "Location A" or call_info.location == "Location C" %}
"result" : {{service_config|pex_update({"pin":"", "guest_pin":"", "allow_guests" : False})|pex_to_json}}
{% else %}
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This service configuration script removes the PIN for participants if their device is registered to a Conferencing Node, but keeps any existing PIN requirements for participants in other locations.
{
{% if service_config %}
"action" : "continue",
{% if call_info.registered %}
"result" : {{service_config|pex_update({"pin":"", "guest_pin":"", "allow_guests" : False})|pex_to_json}}
{% else %}
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This service configuration script adds an Automatically Dialed Participant (ADP) into every conference. Note that this script will override any existing ADPs that may be present in the service_config variable.
This example script also demonstrates how to use the pex_debug_log filter. It writes the original contents of the service_config variable to the support log.
To avoid filling the support log and causing it to rotate, remove all pex_debug_log filters from your scripts as soon as they are working correctly.
{
{% if service_config %}
{{pex_debug_log("service_config=", service_config) }}
"action" : "continue",
"result" : {{service_config|pex_update({"automatic_participants" : [{"remote_alias":"participant@domain.com","local_alias":"policyuser@domain.com","local_display_name":"Local policy ADP","description":"Dial out to an ADP","protocol":"sip","role":"chair","system_location_name":"Location A" }]})|pex_to_json}}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This service configuration script rejects calls from (potentially) malicious User Agents such as "sipvicious" (User Agent "friendly-scanner"). Any calls from User Agents that are in the suspect_uas list will be rejected. You can edit the suspect_uas as required for your environment if appropriate.
{
{# NOTE: not all of the UAs listed are always malicious - they have legitimate uses so you may wish to adapt the list to your particular environment/usage #}
{# NOTE: "cisco" is not used by genuine Cisco equipment - rather by an openH323 based VOIP scanner trying to pass itself off as something respectable #}
{% set suspect_uas = [ "cisco", "friendly-scanner", "sipcli", "sipvicious", "sip-scan", "sipsak", "sundayddr", "iWar", "CSipSimple", "SIVuS", "Gulp", "sipv", "smap", "friendly-request", "VaxIPUserAgent", "VaxSIPUserAgent", "siparmyknife", "Test Agent", "PortSIP VoIP SDK"] %}
{% if service_config %}
{% if call_info.vendor in suspect_uas %}
"action" : "reject",
"result" : {}
{% else %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
You can use the pex_in_subnet filter to test whether a given address is within one or more subnets. This filter could be useful if, for example, you want to place media in a particular location based upon the network location of the participant.
This example template structure sets an address map variable (nl_map) to contain a range of subnets associated with multiple locations, and then uses that variable in multiple pex_in_subnet tests to see if the calling participant's address (call_info.remote_address) is within one of those subnets:
{% set nl_map = {
"Oslo" : [ "10.47.0.0/16", "10.147.0.0/16", "10.247.200.0/24" ],
"New York" : [ "10.1.0.0/16", "10.201.5.0/23"],
"Sydney" : [ "10.61.0.0/16" ],
"London" : [ "10.44.0.0/16" ] } %}
{% if pex_in_subnet(call_info.remote_address, nl_map["Oslo"]) %}
{# Apply Norwegian rules #}
{% elif pex_in_subnet(call_info.remote_address, nl_map["New York"]) %}
{# Apply American rules #}
{% else %}
{# Do the default thing #}
{% endif %}
This example shows a way of locking a conference when the first participant (regardless of Host/Guest) connects. When a conference is locked a Host can manually unlock the conference with *7 or use the Connect app to let someone in.
It works by looking at the Service tag property of the VMR and if the tag value starts with "locked" it will lock the conference. If it doesn't start with "locked" it will not lock the conference. This requires that you set in advance the Service tag to "locked" of the VMRs that you want to automatically lock when the first participant joins.
{
{% if service_config %}
"action" : "continue",
{% if service_config.service_type == "conference" and service_config.service_tag.startswith("locked") %}
"result" : {{service_config|pex_update({"locked":True })|pex_to_json}}
{% else %}
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This example changes the theme based on the time of day in the Europe/London timezone. If it is morning (now.hour < 12) the default theme is used. If it is afternoon the "Afternoon theme" is used, otherwise the "Evening theme" is used.
{
{% set now = pex_now("Europe/London") %}
{% if service_config %}
"action" : "continue",
{% if now.hour < 12 %}
"result" : {{service_config|pex_to_json}}
{% elif now.hour < 18 %}
"result" : {{service_config|pex_update({"ivr_theme_name":"Afternoon theme"})|pex_to_json}}
{% else %}
"result" : {{service_config|pex_update({"ivr_theme_name":"Evening theme"})|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This example allows only registered devices to call into VMRs.
{
{% if service_config %}
{% if service_config.service_type == "conference" %}
{% if call_info.registered %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
{% else %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
If you want to apply the registered-devices-only limitation to calls coming through a specific location only, then you can change
{% if service_config.service_type == "conference" %}
to
{% if service_config.service_type == "conference" and call_info.location == "location name" %}
Note that you have to apply policy profiles to specific locations anyway, but if you have additional policy statements that control other aspects of the call, this allows you to use the same policy profile in all of your locations (but only applies the registration limitation to the nominated location).
In some scenarios it may be necessary to not offer media encryption when dialing out to a specific device, such as older PBXs that do not support SRTP.
This example disables media encryption when calling out to a device sip:user@example.com.
{
{% if service_config %}
{% if call_info.call_direction == 'dial_out' %}
{% if 'sip:user@example.com' in call_info.remote_alias %}
"action" : "continue",
"result" : {{service_config|pex_update({"crypto_mode":"off"})|pex_to_json}}
{% else %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% endif %}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
This example local policy script enables the Teams-like layout as the initial layout for all Microsoft Teams gateway calls. To use the Teams-like layout you must use "view":"teams" and also enable the overlay text and active speaker indicators:
{
{% if service_config and service_config.service_type == "gateway" and service_config.called_device_type == "teams_conference" %}
"action" : "continue",
"result" : {{service_config|pex_update({"enable_overlay_text": true, "view":"teams", "enable_active_speaker_indication":"true"})|pex_to_json}}
{% elif service_config %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
Note that the Teams-like layout:
- Cannot be customized via themes.
- Has the same hardware resource usage requirements as the Adaptive Composition layout.
- Is only suitable for use with Teams gateway calls.
If you want to apply the policy to a specific Call Routing Rule you can test against service_config.name (the Name of the rule), for example: service_config.name == "name of routing rule"
All of the alternative layout names for the view setting are listed here.
This example local policy script enables the display of participant names and selects the Adaptive Composition layout for all Google Meet gateway calls.
{
{% if service_config and service_config.service_type == "gateway" and service_config.called_device_type == "gms_conference" %}
"action" : "continue",
"result" : {{service_config|pex_update({"enable_overlay_text": true, "view":"five_mains_seven_pips"})|pex_to_json}}
{% elif service_config %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
}
If you want to apply the policy to a specific Call Routing Rule you can test against service_config.name (the Name of the rule), for example: service_config.name == "name of routing rule"
All of the alternative layout names for the view setting are listed here.
This example shows how you could route incoming calls directly into a breakout room.
- It assumes that the main "gamesroom" VMR is already configured in Pexip Infinity, with a Name of "gamesroom" (which must have the same value as the name field in the result dictionary).
- It enables two "team" aliases that participants can dial: teama@example.com (to directly join the Team A breakout room) and teamb@example.com (to directly join the Team B breakout room). Note that these should not be configured as additional aliases for the main "gamesroom" VMR.
{
{% if service_config %}
"action" : "continue",
"result" : {{service_config|pex_to_json}}
{% else %}
{% if call_info.local_alias == "teama@example.com" %}
"action" : "continue",
"result" : {"name":"gamesroom", "breakout_uuid":"00000000-0000-0000-0000-000000000001", "breakout":true, "breakout_rooms":false, "breakout_name" : "Team A", "breakout_description": "Team A room", "end_action":"transfer", "end_time":0, "service_tag":"gamesroom_teama", "service_type":"conference", "allow_guests": true, "pin": "4567"}
{% elif call_info.local_alias == "teamb@example.com" %}
"action" : "continue",
"result" : {"name":"gamesroom", "breakout_uuid":"00000000-0000-0000-0000-000000000002", "breakout":true, "breakout_rooms":false, "breakout_name" : "Team B", "breakout_description": "Team B room", "end_action":"transfer", "end_time":0, "service_tag":"gamesroom_teamb", "service_type":"conference", "allow_guests": true, "pin": "4567"}
{% else %}
"action" : "reject",
"result" : {}
{% endif %}
{% endif %}
}
This participant policy masks a participant's telephone number, replacing their alias and name with "0000" and "Telephone User" respectively.
{% set userpart = call_info.remote_alias|lower|pex_regex_replace("^(sip:|sips:|h323:)","") |pex_regex_replace('@.+','') %}
{
"action" : "continue",
{% if userpart|int != 0 %}
"result" : {
"remote_alias": "0000",
"remote_display_name": "Telephone User"
}
{% else %}
"result" : {}
{% endif %}
}
This example participant policy controls entry to a VMR entry based on the participant's identity provider (IDP) attributes and the VMR's service tag.
- VMRs are configured with service tags in the format: IDPattr_attrValue (e.g. "department_IT").
- The policy script separates these tags by the underscore "_" (e.g. "department" and "IT").
- The script checks if IDPattr is in idp_attr_list; if NOT then default continue to allow other VMRs to pass (i.e. they have no service tag or other service tags).
- The script checks if the participant's IDPattr matches attrValue (e.g. does their "department" = "IT").
- If they match the participant is allowed into the VMR, else the participant is rejected.
Other examples of VMR tags to demonstrate how different IDP attributes can be used:
- "jobtitle_Manager": only participants with a job title of "Manager" are allowed into the VMR.
- "givenname_Alice": only participants with givenname of "Alice" are allowed into the VMR.
{
{# Define supported SAML custom attributes #}
{% set idp_attr_list = ["department", "jobtitle", "givenname", "surname"] %}
{# Separate the service_tag #}
{% set tag_fields = call_info.service_tag.split("_", 1) %}
{# Check if first half of tag is in IDP list and then check if second half matches participant's IDP attribute #}
{% if tag_fields|length == 2 and tag_fields[0] in idp_attr_list %}
{% if tag_fields[1] in participant.idp_attributes[tag_fields[0]] %}
"action": "continue",
"result" : {{participant|pex_to_json}}
{% else %}
"action" : "reject",
{% endif %}
{% else %}
"action": "continue",
"result" : {{participant|pex_to_json}}
{% endif %}
}
This example participant policy constructs a participant's remote display name from custom claims / identity provider (IDP) attributes.
It assumes that there are 2 custom claims / IDP attributes: pex_given_name and pex_job_title.
The remote display name is then constructed from the given name and optionally the job title, if they exist for that participant.
{
{% set name = participant.idp_attributes['pex_given_name'] %}
{% set title = participant.idp_attributes['pex_job_title'] %}
"action": "continue",
{% if name and name[0] %}
{% if title and title[0] %}
"result": {{participant|pex_update({"remote_display_name": name[0] + " | " + title[0]})|pex_to_json}}
{% else %}
"result": {{participant|pex_update({"remote_display_name": name[0]})|pex_to_json}}
{% endif %}
{% else %}
"result" : {{participant|pex_to_json}}
{% endif %}
}