SIP Push Notification with OpenSIPS 3.1 LTS [RFC 8599 support][Part II]

Welcome or welcome back to the 2-part series on the RFC 8599 support in OpenSIPS! In this final post (please find the initial one here), you will learn how to enhance your platform with standards-based SIP Push Notification (PN) support using an OpenSIPS 3.1+ release, from high-level design all the way down to specific code additions to your opensips.cfg configuration file.

SIP Architecture

From an architectural point of view, we are proud to let you know that OpenSIPS 3.1 offers two flavours of introducing the PN support:

Monolithic SIP PN Registrar

First, there is the option of building a PN-capable SIP registrar from the ground up. This could be a sensible decision when your existing telephony platform allows sufficient flexibility in order to be able to upgrade or completely replace its SIP registration infrastructure. Alternatively, such a registrar could be an excellent starting point for a PN Proof of Concept: just load the OpenSIPS registrar module, tweak the opensips.cfg a bit so you set up the integration with your PN provider and you’re off to testing it!

The “monolithic SIP PN registrar” architecture

Microservice SIP PN proxy

But not all VoIP platform developers have the luxury described above, of being able to freely replace or upgrade a critical component of the platform: its SIP registration infrastructure, which is sometimes locked in (think contractual agreements, black-box telephony switches, interconnect with a 3rd party that provides the SIP registration service, etc.). For such cases, we have also prepared a more lightweight solution, which easily plugs into your existing SIP infrastructure.

Thus, we have extended the OpenSIPS mid-registrar module so it too benefits from the same PN support as the registrar module! You are now able to interpose a SIP registration proxy between your customers and existing SIP registrars, which not only elegantly extends your service offerings with SIP PN support, but also includes registration throttling capabilities, effectively freeing up those backend registrars from processing unnecessary SIP REGISTER traffic and allowing them to process more calls within the same window of cost.

The “SIP PN proxy-registrar” architecture

Time to get down to the nitty-gritty behind the new additions:

opensips.cfg Configuration

The RFC 8599 support is designed so it requires minimal opensips.cfg scripting:

  • all pn_xxx module parameters have appropriate default values, you just need to enable the support
  • all handling of the incoming and outgoing “advertised PN capabilities” (i.e. the recently introduced SIP Feature-Caps header field) is built-in — there is nothing to script here
  • the save() and mid_registrar_save() functions have been extended to additionally handle PN-capable SIP UAs, without any conflicts with previous registrar features
  • the lookup() and mid_registrar_lookup() functions will now asynchronously launch any required Push Notifications for each PN-enabled contact they find (see more details below). To signify the corner-case where all found contacts are PN-enabled (thus unreachable… yet!), a new success return code has been added (2), which signifies that you must not call t_relay() after the lookup operation, since no branches were actually pushed.
  • in order to process PN for mid-dialog requests, you must call the newly added pn_process_purr() async function

Required OpenSIPS Modules

The following OpenSIPS modules must be loaded together with the PN support:

  • tm (mandatory) – required for its async processing engine
  • event_routing (mandatory) – required for matching an incoming REGISTER event to a suspended INVITE transaction or mid-dialog request that is waiting for a corresponding SIP registration, then either forking a new INVITE branch or resuming the routing of the mid-dialog request
  • event_route (optional) for catching and processing the newly added E_UL_CONTACT_REFRESH event at opensips.cfg level (other event-delivery modules are equally appropriate). The event signifies a need to force a registration refresh from the given SIP UA (in our case, by generating a Push Notification), with the PN coordinates of the device being included in the event payload.

PN Module Parameters

Here is a quick summary of the PN-related module parameters, with all of them being available in both registrar modules:

  • pn_enable – enables a minimal version of the SIP Push Notification support (basic RFC 8599 support)
  • pn_enable_purr – enables the support for generating PNs during mid-dialog request processing (full RFC 8599 support)
  • pn_providers – a list of PN service providers
  • pn_ct_match_params – the list of PN-specific Contact URI parameters which must be present in a REGISTER Contact URI before the sender UA is considered to be PN-compliant. On a “no-match”, the UA will be treated as a regular SIP UA.
  • pn_pnsreg_interval – for devices which are capable of waking up by themselves, this setting denotes an interval prior to registration expiry at which OpenSIPS will recommend the device to re-register.
  • pn_trigger_interval – the interval, prior to registration expiry, at which OpenSIPS will automatically raise an E_UL_CONTACT_REFRESH event, in order to force a registration refresh from the SIP UA
  • pn_skip_pn_interval – denotes a grace period following a re-registration from a PN-capable device, during which PNs will not be sent to the device (it is assumed to be awake)
  • pn_refresh_timeout – pertains to PNs originated during SIP request processing (i.e. not during an auto-generated registration refresh) and denotes the maximum interval that a SIP request/transaction can be paused for in order to wait for a registration refresh from the target device. As a lower-level definition: it is the notify_on_event() timeout.


There is nothing to be changed within the opensips.cfg in order to handle this type of traffic — the save() and mid_registrar_save() will work just as before, being able to deal with both PN-compliant and standard SIP UAs.

However, note that the spec requires the PN registrar/proxy to periodically issue PNs for re-registration, since the PN-equipped devices may be asleep and cannot maintain their registrations alive by themselves. For this to work, we must first enable this timer within usrloc, using the contact_refresh_timer switch. Next, as an example, I will use the event_route module in order to catch and process these events:

loadmodule ""
modparam("usrloc", "contact_refresh_timer", true)

loadmodule ""

event_route [E_UL_CONTACT_REFRESH]
    route(PN_SEND, $(param(uri){param.value,pn-provider}),

# $param(1) - PN provider
# $param(2) - PN PRID
# $param(3) - PN param
route [PN_SEND]
    # send a PN using, for example, exec or rest_client

Initial SIP INVITE Processing

Once the PN support is enabled, both lookup() and mid_registrar_lookup() will start occasionally returning a value of 2 (success) instead of the classic 1 (success). What this means is that it found some contacts, but they are all PN-enabled, thus unreachable for the moment. And while E_UL_CONTACT_REFRESH events have been already triggered asynchronously for them, we must not call t_relay(), since no immediately routable contacts were found. Or, in opensips.cfg code:

# do lookup with method filtering
lookup("location", "m");
$var(rc) = $retcode;
switch ($var(rc)) {
case 1:
    # we found at least 1 non-PN contact!
    $var(do_relay) = true;
case 2:
    # success, but all contacts are PN-enabled, so we're
    # sending PNs / awaiting re-registrations from them
    $var(do_relay) = false;
    xlog("L_INFO", "DBG: no contacts found ($var(rc))\n");
    t_reply(404, "Not Found");
if ($var(do_relay) && !t_relay())
    send_reply(500, "Internal Server Error");

Mid-Dialog Request Processing

In order to enable the support for sending PNs during mid-dialog request processing, you must switch pn_enable_purr to true. By doing so, the proxy will advertise some opaque PURR (Proxy-Unique Registration Reference) value to each PN-capable SIP UA, as they register:

Auto-generated PURR in a 200 OK reply to a REGISTER

Since the PN coordinates of each SIP device are meant to be private, they are only transmitted between UA and PN proxy, during registration. During an INVITE negotiation, UAs will avoid leaking their PN coordinates to each other, opting to advertise their PURR value instead, by making use of the “;pn-purr=” Contact URI parameter of the INVITE or 200 OK. During each call setup, if a UA includes its “;pn-purr” value in its advertised Contact URI, it signifies that it expects to be awoken by a PN for each mid-dialog request originated by the opposite party.

Within your opensips.cfg, make sure to also generate a PN and put the mid-dialog request on async hold any time its R-URI or topmost Route contains the“;pn-purr” URI parameter, by simply invoking pn_process_purr():

if (has_totag()) {
    if (is_method("ACK") && t_check_trans()) {

    if (!loose_route()) {
        send_reply(404, "Not Found");

    if (!is_method("ACK"))
        async (pn_process_purr("location"), resume_route);


route [resume_route] {
    $var(rc) = $rc;
    xlog("pn_process_purr() finished with $var(rc)\n");

Wrapping Up

Although the implementation apparently looks solid and complete, it was tested, after all, using custom, sipp-based SIP UAs. The real test is when we will proof-test it with some real-life mobile devices that implement RFC 8599. And if you’re reading this, I would assume you have some knowledge on the topic and I would be delighted to find out your opinion on the current incarnation of the draft. What is OK? What is not? How would you see things differently? Is the VoIP industry quickly evolving towards unanimously supporting the draft or will the garden walls still stand tall even in 2030? As always, direct any questions or feedback to!


13 thoughts on “SIP Push Notification with OpenSIPS 3.1 LTS [RFC 8599 support][Part II]

  1. Hello, do you have any extra comments(about the configuration) on using push notification configuration above with the topology_hiding? after call is connected and client side press hold button(a new invite to opensips side), the ACK from client to opensips doesnt go to the next hop. i feel that topology_hiding_match doesnt remember the correct next hop this time.


    1. Hi! There is no connection between the PN support and topology_hiding(), everything should work just fine. Regarding mid-dialog requests, remember that they can be subject to routing issues due to NAT’ed Contact headers just like initial requests, so make sure to call fix_nated_contact() appropriately both on the Re-INVITE and the 200 OK!


      1. Hi,

        After strugling and no result, i have just upgraded from 3.1.1 to 3.1.2 and issue has been solved. But now the push notification stop working. while i am at 3.1.1, opensips mid_registrar_lookup(“location”) function could understand that the client is push enabled and acts accordingly( dont relay and wait for register). but after upgrading to 3.1.2 now, according to mid_registrar_lookup(“location”) function, the end user is not a pn enabled end user. but sure it is.

        PS: i did my tests with a recompiled linphone-android and did little modifications so that each time, it gets a push, it sends register to opensips. but after 3.1.2 since opensips dont think it is a pn enabled, push is never triggered.

        here is the example of contact in mysqldb location table.:


        is this contact information is not compatible with 3.1.2 but it is compatible with 3.1.1?



      2. Tip: to figure out if a contact was detected as PN-enabled or not, do an “ul_dump”. If it’s PN-enabled, the “Flags” will have a value of 4, otherwise they’ll be set to 0 (regular SIP contact).

        So if the flag is set to 0 whereas you’d expect 4, please send a full sample of DBG logs during registration of that contact to I’m sure we’ll figure it out 🙂


      1. I have been playing with Linphone and Opensips myself lately using RFC 8599 PN. So far in my experience Linphone does not stay registered for very long, only a handful of re-registration cycles. I haven’t determined where the fault lies yet, whether it’s opensips, linphone, or firebase, but I suspect that either a PN to refresh registration is not making it to the app, or more likely the app is missing or ignoring it for whatever and not re-registering.

        This does seem like a vulnerability in the implementation though. I don’t know if RFC 8599 addresses this or not as I’ve only gotten as far following the opensips blogs and docs for configuring. There does seem to be a need for something to detect when a re-registration is missed though, or when a PN fails to send, and to retry or repeat the PN to ensure that a deice stays registered.

        Would love to hear about others experiences with this.


  2. Hi Liviu,

    Reading your blog posts, it’s not clear how OpenSIPS and FCM establish a trusted relationship so FCM knows OpenSIPS is allowed to send a PN. Reading the RFC I can see this is all provided by the SIP UA, e.g. the SIP UA populates pn-param with the Firebase Project Id.

    Nice and simple!


    Liked by 1 person

    1. Hey, Gavin!

      The interconnect between OpenSIPS and FCM or other PN providers is outside the scope of the RFC. During my tests, I mocked the entire process of sending a PN, as the re-REGISTER would be automatically generated by sipp, in a timely fashion. As we gain more traction for the PN proxy, with some actual business requirements, an example tutorial on how to interact with a specific provider will definitely pop up on the wiki, or maybe even here!


      Liked by 1 person

      1. Thanks for explaining. Assuming FCM as this targets iOS, Android and web, the pn-prid ties the FCM token to the project on FCM. This means it’s up to us to implement, say a lua script, to use this pn-prid and craft the correct form of FCM message (probably a data message, not notification) and a schema that the SIP UA is looking for, then hook that data message into the lifecycle of the SIP UA?

        Where would we call that lua script if I’m on the right track? I will re-read the flows in part 1 & 2 and RFC.


      2. In the “event_route” sample code I provided, notice the “PN_SEND” route, which takes 3 arguments: provider, prid and param. That route should do all the PN-related magic: authenticating (if necessary) to FCM, then triggering the PN.


  3. Just about on to testing 3.1 in our labs with our new mobile app. Should be this weekend/next week. Will let you know when it’s working.

    Liked by 1 person

Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s