Setting up FreePBX to send a X-Tenant SIP header for Skyetel Tenant tracking



  • One nice thing about the tenant functionality that @Skyetel is implementing is that it is designed to be tracked (for outbound calls) via a SIP header. This is nice, because it is a simple bit of programming that needs to be set up and everything will just work.

    Normally, you have a single Trunk set up for your SIP provider and all of your calls route in and out over this trunk.

    Tracking inbound calls is easy. You have the DID that the call was placed to. But how do you track the outbound calls?

    With VoIP.ms, and their multitude of POPs, I simple made multiple accounts and registered a separate trunk for each separate billing I needed. But I had to choose a different POP for each in order for the outbound calls to flow into the right place.

    With Skyetel's tenant functionality, you can associated each DID with a tenant inside the SKyetel portal. This easily handles your inbound calling. You can also chose to use an endpoint instead of a phone number. If you do this, all calls in and out of that endpoint will be associated to the tenant. This works fine for resellers, where each PBX is a real tenant. But that is not how I do things. My use case is a single client wanting to have multiple offices independently tracked. So there is only one PBX, but I need 4 separate historical bills.

    So I link the phone number to a tenant and then I set the X-Tenant SIP header.

    In FreePBX, the easiest thing for me, was to base the X-Tenant value on the outbound route name.

    In order to get outbound route name in a custom dialplan, you first have to up the "NoOp" level from 0 to 1 in the advanced settings.

    ee59c4f9-739b-413e-8416-be54a3281912-image.png

    Scroll down to Dialplan and Operation

    a7e07793-5047-403b-a102-bda8e25d1814-image.png

    Then find NoOp Traces in Dialplan and change it from 0 to 1
    a964d7b1-3b30-4ee5-a76f-35fe864f7f41-image.png

    Submit and Apply Changes like always.

    Then move to Admin -> Config Edit
    dc3eea42-64db-438e-8ca2-34ad25812031-image.png

    Click on extensions_custom.conf. Assuming you have never done anything it will be blank and the icon will not have the lines in it, like this screenshot.
    6a9f468e-5c57-44d3-a23c-04b119f9272f-image.png

    Now over in the edit box on the right, you drop in your custom dial plan.
    These three lines are the minimum, and will apply the same X-Tenant header to all outbound trunk calls.

    [macro-dialout-trunk-predial-hook]
    exten => s,1,NoOp(Entering user defined context [macro-dialout-trunk-predial-hook] in extensions_custom.conf)
    exten => s,n,GoSub(func-set-sipheader,s,1(X-Tenant,YOUR.TENANT.ID))
    exten => s,n(exit_macro),MacroExit()
    

    In fact you don't even need to change the NoOp setting in Advanced settings if this is all you want to do. But if this is all you need, then you could also just use the endpoint link up in the Skyetel portal and be done.

    So on to a more thorough example.
    This PBX has multiple trunks. but only one for Skyetel and it is named Skyetel.
    I only want to apply the X-Tenant header when the Skyetel trunk is being used.

    This PBX instance has many outbound routes, but I ensure that they all start with the either STL, Cape, Jeff City, or Quincy. I will use a different X-Tenant values based on those four things.

    The comments within the dialplan should be self explanatory, but please post if you have any questions.

    [macro-dialout-trunk-predial-hook]
    ; Much thanks to Lorne Gaetz with Sangoma for answering questions.
    ; https://community.freepbx.org/t/need-to-add-sip-header-to-outbound-calls-on-pjsip-trunk/60441/4
    exten => s,1,NoOp(Entering user defined context [macro-dialout-trunk-predial-hook] in extensions_custom.conf)
    ; Get trunk name. If it is Skyetel determine a tenant, if not, exit.
    exten => s,n,ExecIF($["${OUT_${DIAL_TRUNK}_SUFFIX}"!=""]?Set(trunk_name=${OUT_${DIAL_TRUNK}_SUFFIX}):Set(trunk_name=${OUT_${DIAL_TRUNK}}))
    exten => s,n,GotoIf($["${trunk_name}"=="@Skyetel"]?determine_tenant:exit_macro)
    ; Based on outbound route name, add a SIP header X-Tenant=X
    exten => s,n(determine_tenant),NoOp(We are heading for the Skyetel Trunk, determine the X-Tenant based on outbound route)
    ; Set x_tenant = to tenant.stl by default.
    exten => s,n,Set(x_tenant=tenant.stl)
    ; Change it if the call is on a Cape, Jeff City, or Quincy outbound route.
    exten => s,n,ExecIf($["${OUTBOUND_ROUTE_NAME:0:4}"=="Cape"]?Set(x_tenant=tenant.cape))
    exten => s,n,ExecIf($["${OUTBOUND_ROUTE_NAME:0:9}"=="Jeff City"]?Set(x_tenant=tenant.jeffcity))
    exten => s,n,ExecIf($["${OUTBOUND_ROUTE_NAME:0:6}"=="Quincy"]?Set(x_tenant=tenant.quincy))
    exten => s,n,NoOp(Based on the outbound route name of ${OUTBOUND_ROUTE_NAME}, the X-Tenant is being set to ${x_tenant}.)
    exten => s,n,GoSub(func-set-sipheader,s,1(X-Tenant,${x_tenant}))
    exten => s,n(exit_macro),MacroExit()
    


  • I used ExecIf for the outbound route logic because using GotoIf would be more lines of code getting written. But GotoIf would actually process fewer lines of code if you have more ExecIf than a few. It was a conscious decision/trade off.

    This is the logic for GotoIf instead.

    ; Change it if the call is on a Cape, Jeff City, or Quincy outbound route.
    exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME:0:4}"=="Cape"]?tenant_cape)
    exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME:0:9}"=="Jeff City"]?tenant_jeffcity)
    exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME:0:6}"=="Quincy"]?tenant_quincy))
    ; Nothing match goto set_tenant
    exten => s,n,Goto(set_tenant)
    exten => s,n(tenant_cape),Set(x_tenant=tenant.cape)
    exten => s,n,Goto(set_tenant)
    exten => s,n(tenant_jeffcity),Set(x_tenant=tenant.jeffcity)
    exten => s,n,Goto(set_tenant)
    exten => s,n(tenant_quincy),Set(x_tenant=tenant.quincy)
    exten => s,n,Goto(set_tenant)
    exten => s,n(set_tenant),NoOp(Based on the outbound route name of ${OUTBOUND_ROUTE_NAME}, the X-Tenant is being set to ${x_tenant}.)
    exten => s,n,GoSub(func-set-sipheader,s,1(X-Tenant,${x_tenant}))
    exten => s,n(exit_macro),MacroExit()
    

    So ExecIf executes all three ExecIf statements, and if one is true, one of the Set statements. So always 3 and sometimes 4 operations everytime a call is placed.

    With the GotoIf it can execute 3 checks, then a Goto if nothing matches.
    Or 1 check, a gotoif, a set, and a goto for 4 operations. Both of these were 4 operations.
    If the second gotoif kicks, it means 5 total operations, and 6 total operations if the third one is applied.

    So if you apply the GotoIf logically based on how often they are called, with the most used ones at the beginning of the logic flow, you can drastically reduce the number of calls compared to ExecIfif there are a lot of routes.



  • Also, it was my assumption that @Skyetel tracks all inbound and outbound if you choose to associate the endpoint group to a Tenant. The documenation was unclear.

    This is all still decently early in release for them though, so no complaints about documentation from me. Mine is far worse.



  • This is fully working now. I tested calls and saw all of the correct X-Tenant headers in the outbound INVITE messages
    2fa7d200-a790-4d42-bda6-e26628f30c19-image.png

    Outbound routing changed as of now, to give a short time of low call volume as a pseudo test.



  • Not trying to threadjack.
    Just an fyi, in FusionPBX, this is included by default.



  • @JaredBusch nice! What tool are you using in that screenshot to look at call flow?



  • @fuznutz04 said in Setting up FreePBX to send a X-Tenant SIP header for Skyetel Tenant tracking:

    @JaredBusch nice! What tool are you using in that screenshot to look at call flow?

    sngrep https://github.com/irontec/sngrep



  • @FATeknollogee Thanks!



  • @FATeknollogee said in Setting up FreePBX to send a X-Tenant SIP header for Skyetel Tenant tracking:

    Not trying to threadjack.
    Just an fyi, in FusionPBX, this is included by default.

    That has nothing to do with anything as the thread was specifically about setting up FreePBX.

    FusionPBX is not ever Asterisk based so nothing especially matters about what works in that.



  • @fuznutz04 said in Setting up FreePBX to send a X-Tenant SIP header for Skyetel Tenant tracking:

    @FATeknollogee Thanks!

    Sangoma has included it by default since FreePBX 14. It was available via yum in FreePBX 13.



  • All outbound calls were routed out to @Skyetel for the entire day.

    Users were completely unaware of any changes. This is how things are supposed to work.

    535 minutes used.
    61852163-fe13-44c9-94a3-b99ec02c0842-image.png

    Only 5 simultaneous channels.
    c987f00a-2f56-45d6-8b22-455a3bdc33bd-image.png



  • @JaredBusch we did a port over to Skyetel for a busy utility today, too.