Transformations
CarrierX Core API allows the partners to additionally manipulate the data of the incoming and outgoing calls using transformations.
What Are Transformations and Why Use Them
The users might find transformations quite complicated, but the concept behind the transformations is very simple: you can check the inbound or outbound call data and modify it so that it better matched your purposes.
Why would one even bother with this?
It might happen, that the application you use requires the call data (e.g., the phone number in a certain header field) to have a specific format. Rather than process it on the application side (especially, when you are not the developer of that application, or cannot modify it due to some other reasons), you can apply the transformations within CarrierX and send the data to your application in the format it requires.
Or vice versa, sometimes the application might send the data to CarrierX in a format it cannot accept properly, and this data format cannot be changed on the application side. You can use the transformations to adapt the data into the CarrierX compatible format for the correct work.
The logic of a simple rewrite and set transformation looks like the following:
More complex transformations include lookup transformations and conditional transformations. The former will lookup for the routing number and transform the call fields depending on the result. The latter will allow the users to check the incoming data against some condition and then apply the matching transformations (simple, lookup, or other conditional) depending on the check results.
You can also combine different types of transformations applying them one-by-one, e.g., first perform lookup, then use some simple transformation, then check for a condition and, if true, use more simple transformations, among which are the STIR/SHAKEN, reject transformations, and others.
Transformations Levels
Depending on the object where the transformations are set, they can be applied to different calls on different levels.
These transformations levels include:
- Partner—transformations set to the Partner object will be applied to all the calls for the selected partner.
- DID—transformations set to the DID object will be applied to the calls to/from the specified phone number, trunk groups, trunks, and endpoints associated with this phone number.
- Prefix—transformations set to the Prefix object will be applied to the calls routed by the specified prefix, as well as the trunk groups, trunks, and endpoints for which this prefix routes the traffic.
- Trunk Group—transformations set to the Trunk Group object will be applied to the calls routed through the specified trunk group, as well as the trunks inside this trunk group and the endpoints associated with the trunks within this trunk group.
- Trunk—transformations set to the Trunk object will be applied to the calls routed through the specified trunk, as well as the endpoints associated with this trunk.
- Endpoint—transformations set to the Endpoint object will be applied to the calls routed to or from this endpoint.
This way, you can use the partner level to set transformations which will be applied to all the calls (inbound or outbound) for which you use your rented phone numbers or specified prefixes. These transformations will be active throughout CarrierX system for you as a partner.
To narrow the rules to a specific DID or prefix and other objects associated with it, and at the same time exclude other phone numbers or prefixes, use transformations on DID/prefix level.
Trunk groups, trunks, and endpoints levels will help apply the transformations to the call data passing through them, and will not affect other trunk groups, trunks or endpoints.
Transformations Format
Any transformation is an object with three fields:
-
action
is the name of the transformation and a short description of an action this transformation performs, i.e., what type of modification will be made. For example, the transformation with thereject
action actually rejects the call returning one of the reasons set in theoperands
. -
direction
defines the direction of the call this transformation will be applied to. For example, the transformation with theinbound
direction will be applied to the incoming calls only, and will not affect the outgoing calls. Refer to this section for more information on CarrierX definitions of the inbound and outbound calls. -
operands
are the items to which theaction
is applied. Some of the operands can include only set values, and some accept both set values and regular expressions.
action
and direction
are the required fields when creating a transformation.
The presence and number of operands
depend on the action
. While the operands
field itself must always be present, the number of operands differ from one transformation to another.
A typical transformation will have the following structure:
{
"action": "set_header_parameter",
"direction": "any",
"operands": ["P-Charging-Vector","orig-ioi","privateSIP"]
}
We will take a closer look at different transformations and their attributes in the section below.
Transformations and Their Examples Explained
In this section we will explain the main types of CarrierX transformations and the operands used with them. We will also see the examples for main transformation types to understand what they do more easily.
Header Transformations
Header Transformations are used to transform single or multiple call headers or header parameters. There are two types of header transformations: rewrite transformations and set transformations.
Rewrite Transformations
Quite often it is necessary to rewrite a single or multiple call headers or header parameters. This can be done with the help of rewrite transformations:
The rewrite transformation will analyze the call data, find the source or destination phone number, or the header set by the action
and, depending on the action
, will replace either the phone number/header or some of the header parameters.
rewrite_from/rewrite_to
The rewrite_from
transformation will replace the phone number which is the originator of the call (the source phone number). Its syntax looks like the following:
{
"action": "rewrite_from",
"direction": "any",
"operands": [
"pattern",
"replace"
]
}
The rewrite_to
transformation does the same to the phone number which is the destination of the call. Its syntax looks like the following:
{
"action": "rewrite_to",
"direction": "any",
"operands": [
"pattern",
"replace"
]
}
Example
Initially, the From
header looks like this:
From: "John Smith" <sip:5162065613@12.7.193.174>;tag=as062a2e2a
The transformation syntax will be the following:
{
"action": "rewrite_from",
"direction": "any",
"operands": [
"^([2-9]\\d{9})$",
"1\\1"
]
}
- The first operand (
pattern
=^([2-9]\d{9})$
) is the regular expression used to find out whether the phone number lacks1
at the beginning. - The second operand (
replace
=1\1
) adds1
to the beginning of the found combination of digits (i.e., the phone number) in case the pattern matches.
After the transformation, the From
header will look the following way:
From: "John Smith" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
The same example for the rewrite_to
transformation will only differ in the action
value.
rewrite_from_header_param/rewrite_to_header_param
The rewrite_from_header_param
transformation will replace the parameters of the From
header with the data specified. Its syntax looks like the following:
{
"action": "rewrite_from_header_param",
"direction": "any",
"operands": [
"parameter",
"pattern",
"replace"
]
}
The rewrite_to_header_param
transformation does the same to the To
header. Its syntax looks like the following:
{
"action": "rewrite_to_header_param",
"direction": "any",
"operands": [
"parameter",
"pattern",
"replace"
]
}
Example
Initially, the From
header looks like this:
From: "John Smith" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
The transformation syntax will be the following:
{
"action": "rewrite_from_header_param",
"direction": "any",
"operands": [
"cnam",
"(.{1,14})",
"\\1*"
]
}
- The first operand (
parameter
=cnam
) will make the transformation look for thecnam
parameter of theFrom
header. - The second operand (
pattern
=(.{1,14})
) defines the the match, it will search for the caller ID in thecnam
parameter. - If such a match is found, it will be replaced. The
\\1*
regular expression will add the*
character to the end of the caller ID.
After the transformation, the From
header will look the following way:
From: "John Smith*" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
The same example for the rewrite_to_header_param
transformation will only differ in the action
value.
rewrite_header
The rewrite_header
transformation will replace the header (or a part of it) you specify as an operand with the new values, or create it, in the case the call data lacks it. Its syntax looks like the following:
{
"action": "rewrite_header",
"direction": "any",
"operands": [
"header",
"pattern",
"replace",
"default"
]
}
Example
Initially, the X-Custom-Header
header looks like this:
X-Custom-Header: sip:10.1.5.200:5060
The transformation syntax will be the following:
{
"action": "rewrite_header",
"direction": "any",
"operands": [
"X-Custom-Header",
":5060",
":6060",
"sip:10.1.5.200:6060"
]
}
- The first operand (
header
=X-Custom-Header
) specifies the header which must be found - The second operand (
pattern
=:5060
) defines the pattern to find the port which will be replaced. - The third operand (
replace
=:6060
) specifies the value with which thepattern
must be replaced. - We also add the fourth operand (
default
=sip:10.1.5.200:6060
) to make sure that in the case the header is missing from the call data it will be created and set to this value.
After the transformation, the X-Custom-Header
header will look the following way:
X-Custom-Header: sip:10.1.5.200:6060
rewrite_header_parameter
The rewrite_header
transformation will replace the parameter of the header you specify as an operand with the new value, or create it, in the case the call data lacks it. Its syntax looks like the following:
{
"action": "rewrite_header_parameter",
"direction": "any",
"operands": [
"header",
"parameter",
"pattern",
"replace",
"default"
]
}
Example
Initially, the Remote-Party-ID
header looks like this:
Remote-Party-ID: "John Smith" <sip:15162065613@10.1.10.190>;party=calling;privacy=name;screen=no
The transformation syntax will be the following:
{
"action": "rewrite_header_parameter",
"direction": "any",
"operands": [
"Remote-Party-ID",
"privacy",
"name",
"cnam",
"cnam"
]
}
- The first operand (
header
=Remote-Party-ID
) specifies the header which must be found. - The second operand (
parameter
=privacy
) defines the parameter of the selected header, which must be modified. - The third operand (
pattern
=name
) defines the pattern to find the port which will be replaced. - The fourth operand (
replace
=cnam
) specifies the value with which the pattern must be replaced. - We also add the fifth operand (
default
=cnam
) to make sure that in the case the header is missing from the call data it will be created and set to this value.
After the transformation, the Remote-Party-ID
header will look the following way:
Remote-Party-ID: "John Smith" <sip:15162065613@10.1.10.190>;party=calling;privacy=cnam;screen=no
Set Transformations
In the case the required parameters are missing from the call data, they can be added to it with the help of set transformations.
set_header
The set_header
transformation will add a new header you specify as an operand to the call data. Its syntax looks like the following:
{
"action": "set_header",
"direction": "any",
"operands": [
"header",
"value"
]
}
Example
Initially, the X-Custom-Header
header is not present in the call data.
The transformation syntax will be the following:
{
"action": "set_header",
"direction": "any",
"operands": [
"X-Custom-Header",
"sip:10.1.5.200:5060"
]
}
After the transformation, the X-Custom-Header
header will look the following way:
X-Custom-Header: sip:10.1.5.200:5060
set_header_parameter
The set_header_parameter
transformation will add a new parameter to the header you specify as an operand. Its syntax looks like the following:
{
"action": "set_header_parameter",
"direction": "any",
"operands": [
"header",
"parameter",
"value"
]
}
Example
Initially, the P-Charging-Vector
header looks like this:
P-Charging-Vector: icid-value=ab5fc4ee59;icid-generated-at=12.7.193.171
The transformation syntax will be the following:
{
"action": "set_header_parameter",
"direction": "any",
"operands": [
"P-Charging-Vector",
"orig-ioi",
"privateSIP"
]
}
- The first operand (
header
=P-Charging-Vector
) specifies the header, to which the parameter will be added. - The second operand (
parameter
=orig-ioi
) defines the parameter to add. - And the third operand (
value
=privateSIP
) sets the required value to the new header parameter.
After the transformation, the P-Charging-Vector
header will look the following way:
P-Charging-Vector: icid-value=ab5fc4ee59;icid-generated-at=12.7.193.171;orig-ioi=privateSIP
Lookup Transformations
It is also possible to perform more complex transformations, e.g., lookup the routing number and transform the call fields depending on the result.
lookup_rn
The lookup_rn
information will lookup the routing number and return it so that it could be used instead of the source or destination number. Its syntax looks like the following:
{
"action": "lookup_rn",
"direction": "inbound",
"operands": [
"force",
"always",
"phonenumber",
"destination",
"on_failure",
"input_format",
"output_format_domestic",
"output_format_international",
"output_format_guess_not_found"
]
}
Example
Initially, the From
header looks like this:
From: "John Smith" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
The transformation syntax will be the following:
{
"action": "lookup_rn",
"direction": "inbound",
"operands": [
"true",
"true",
"{{src}}",
"from",
"ignore",
"guess",
"e164_with_plus",
"e164_with_plus",
"e164_with_plus"
]
}
- The first operand (
force
=true
) will make the transformation to always lookup the routing number, even if thenpdi
field is already set in the SIP URI. - The second operand (
always
=true
) will always add the routing number to the destination field. - The third operand (
phonenumber
={{src}}
) will lookup the source number (or the calling party). - The fourth operand (
destination
=from
) will set the field where to store the routing number. - The fifth operand (
on_failure
=ignore
) will define the behavior on the transformation lookup request failure, setting it toignore
. - The sixth operand (
input_format
=guess
) will try and guess the phone number input format before performing the lookup for it. - The seventh operand (
output_format_domestic
=e164_with_plus
) will define the output format for the found number as E.164 (11 digits) with a+
at the beginning in the case the number is considered domestic. - The eighth operand (
output_format_international
=e164_with_plus
) will define the output format for the found number as E.164 (11 digits) with a+
at the beginning in the case the number is considered international. - And the ninth operand (
output_format_guess_not_found
=e164_with_plus
) will define the output format for the phone number as E.164 (11 digits) with a+
at the beginning in the case the routing number could not be found.
Note, that fields in double curly brackets are called expandable macro variables. To see the full list of them, please refer to the Expandable Macro Variables Appendix.
After the transformation, the From
header will look the following way:
From: "John Smith" <sip:+16466531111@12.7.193.174>;tag=as062a2e2a
STIR/SHAKEN Transformations
The STIR/SHAKEN transformations are used to check the calling party, validate it, and add the appropriate mark to the call data depending on the attestation result. Refer to the STIR/SHAKEN section for more information about this.
stir_validate
The stir_validate
transformation will validate the call and return data that can be used with other transformations.
Its syntax looks like the following:
{
"action": "stir_validate",
"direction": "any",
"operands": []
}
Example
{
"action": "stir_validate",
"direction": "any",
"operands": []
}
As a result, it will return the following data:
{{stir_attest}}
for the attestation level used for the call.{{stir_origid}}
for the ID of the call originator.{{stir_verstat}}
for the TN validation result (available values includeTN-Validation-Passed
,TN-Validation-Failed
, andNo-TN-Validation
).
Note, that fields in double curly brackets are called expandable macro variables. To see the full list of them, please refer to the Expandable Macro Variables Appendix.
The system saves the verification results to the stir_attest
, stir_origid
, and stir_verstat
attributes of the Call Detail Record object.
These results can be used, for example, with the conditional transformations for the further actions that will depend on the validation result.
Conditional Transformations
The conditional transformations are normally used when there is a more complex logic behind them. They allow the users to check the incoming data against some condition and then apply the matching transformations (rewrite, lookup, other conditional, etc) depending on the check results.
Multiple conditional transformations can be applied to one and the same call, each of them will trigger some other transformation depending on the rule matched.
if_match
The if_match
transformation will add conditions based on which other transformations are applied. Its syntax looks like the following:
{
"action": "if_match",
"direction": "inbound",
"operands": [
"value",
"match",
"action",
"arg1",
"argX"
]
}
Example
The transformation syntax will be the following:
{
"action": "if_match",
"direction": "inbound",
"operands": [
"{{stir_verstat}}",
"TN-Validation-Failed",
"reject",
"forbidden"
]
}
- The first operand (
value
={{stir_verstat}}
) specifies the value against which the match will be done. In this example, it might be equal to one of the following values:TN-Validation-Passed
,TN-Validation-Failed
, orNo-TN-Validation
. These values are returned when the stir_validate transformation is used. - The second operand (
match
=TN-Validation-Failed
) sets the value the transformation will be looking for. If this value matches the first operand, the action from the next operands will be executed. - The third operand (
action
=reject
) defines the transformation that will be executed if the condition matches. - And the fourth operand (
arg1
=forbidden
) is the operand required by the transformation defined in theaction
(in this example it isreject
).
Note, that fields in double curly brackets are called expandable macro variables. To see the full list of them, please refer to the Expandable Macro Variables Appendix.
The result of this transformation (if the condition is true) will be that the call that failed the validation will be rejected with the forbidden
reason.
Other Transformations
reject
The reject
transformation will drop the calls with one of the supported reasons.
Its syntax looks like the following:
{
"action": "reject",
"direction": "inbound",
"operands": [
"reason",
"message"
]
}
Example
The transformation syntax will be the following:
{
"action": "reject",
"direction": "inbound",
"operands": [
"busy-here",
"My reason for rejecting the call"
]
}
-
The first operand (
reason
=busy-here
) defines the reason that will be passed to the inbound call, and the call will be dropped. -
The second operand (
message
=My reason for rejecting the call
) defines the additional message that will be sent to the caller as a part of theCall-Info
SIP header:
Call-Info: "My reason for rejecting the call"
set_user_data
The set_user_data
transformation will add custom user data to the call detail records. The transformation saves the data as a JSON object to the user_data
attribute of the Call Detail Record object. Its syntax looks like the following:
{
"action": "set_user_data",
"direction": "any",
"operands": [
"key",
"value"
]
}
Example
The transformation syntax will be the following:
{
"action": "set_user_data",
"direction": "any",
"operands": [
"identity",
"{{SipHeader_Identity}}"
]
}
- The first operand (
key
=identity
) specifies the name of the key that will be used to store the user-defined data. - And the second operand (
value
={{SipHeader_Identity}}
) takes the value from the SIP Identity header and sets it to the new key. Fields in double curly brackets are called expandable macro variables. To see the full list of them, please refer to the Expandable Macro Variables Appendix.
After the transformation, the user_data
attribute of the call detail record will look the following way:
{
"user_data": {
"identity": "eyJ0eXAiOiJwYXNzcG9ydCIsImFsZyI6I...alg=ES256;ppt=shaken"
}
}
sms_normalize
By default, all phone numbers for the SMS Object are always treated as E.164 international numbers.
The sms_normalize
action is used to add a corresponding country prefix to the SMS Object from
and/or to
fields to allow local in-country formats. This transformation is based on the Phonenumber Lookup object mechanism and the Phonenumber Lookup logic.
Its syntax looks like the following:
{
"action": "sms_normalize",
"direction": "outbound",
"operands": [
"guess",
"country_code",
"field",
"on_failure",
"output_format"
]
}
Example
The transformation syntax will be the following:
{
"action": "sms_normalize",
"direction": "outbound",
"operands": [
"e164",
"USA",
"to",
"ignore",
"e164"
]
}
-
The first operand (
guess
=e164
) tells the system which to prefer if thecountry_code
lookup step (see below) results in finding out that the DID this transformation is applied to is both: a valid E.164 and a valid domesticin_country
phone number. The default value ise164
. In this case the system will make a guess that the DID is an E.164 number. Another possible value for this operand isin_country
in which case the system will try to make a guess that the DID is a domestic number. If theguess
step does not bring any result, the system proceeds to theon_failure
step. -
The second operand (
country_code
=USA
) tells the system that before routing any outbound SMS to a phone number without the+
prefix, it should first check and verify whether it is a valid number in that country. If a valid number is found, the system will add the corresponding country code prefix to the phone number and the SMS will be processed further, i.e. for the USA, the+1
prefix will be added, and no further checks will be made.
If no valid number is found, however, the system will try to find a match for this number among domestic (in-country) and E.164 numbers. Here, tree options are possible:- if a match is found and the number is only a valid domestic number or only a valid E.164 number, the SMS will be processed further, without any more checks. If the number is a domestic one, the corresponding code of that country will be added (i.e. such domestic number will be converted to the E.164 format), and if the number is E.164, it will be processed as is, without changes.
- if the number matches both, domestic and E.164 formats, the system will switch to the
guess
step (see above). - if no match is found, the system will switch to the
on_failure
step (see below) and will return a4xx
error.
-
The third operand (
field
=to
) tells the system that this transformation will be applied to theto
field of the affected SMS Object. Another possible value you could use here isfrom
. -
The fourth operand (
on_failure
=ignore
) tells the system to ignore the fact that the phone number has not been identified successfully and to continue with sending the SMS. -
The fifth operand (
output_format
=e164
) tells the system to output the destination DID in the E.164 format.
The sequence of operands in this transformation may look confusing at first. But here is a flow chart of how the sms_normalize
transformation works.
Chaining Transformations
Sometimes, it might be necessary to use multiple transformations rather than a single one. In this case, the actions can be chained, and the next transformations will either be using the results of the previous ones, or cover cases which the previous transformations missed.
Let’s see what the following succession of transformations does:
"transformations": [
{
"action": "stir_validate",
"direction": "inbound",
"operands": []
},
{
"action": "set_header",
"direction": "inbound",
"operands": ["X-StirResult", "{{stir_verstat}}-{{stir_attest}}"]
},
{
"action": "if_match",
"direction": "inbound",
"operands": ["{{stir_verstat}}:{{stir_attest}}", "TN-Validation-Passed:[AB]", "rewrite_from_header_param", "cnam", "(.{1,14})", "\\1*"]
},
{
"action": "if_match",
"direction": "inbound",
"operands": ["{{stir_verstat}}", "No-TN-Validation", "rewrite_from_header_param", "cnam", ".*", "POSSIBLE FRAUD"]
},
{
"action": "if_match",
"direction": "inbound",
"operands": ["{{stir_verstat}}", "TN-Validation-Failed", "reject", "forbidden"]
}
]
-
The stir_validate transformation validates the inbound call and returns the
{{stir_attest}}
and{{stir_verstat}}
variables, which will be used with the next transformations. -
The set_header transformation adds the
X-StirResult
header to the call data and sets its value to the received{{stir_verstat}}-{{stir_attest}}
(e.g.,TN-Validation-Passed-A
).
Note, that fields in double curly brackets are called expandable macro variables. To see the full list of them, please refer to the Expandable Macro Variables Appendix.After the transformation
X-StirResult: TN-Validation-Passed-A
-
The first if_match transformation checks the following condition: if the call is validated
A
orB
({{stir_attest}}
=A
or{{stir_attest}}
=B
and{{stir_verstat}}
=TN-Validation-Passed
), the rewrite_from_header_param transformation will add a special*
character to the endcnam
parameter of theFrom
header to signify a trusted caller.Before the transformation
X-StirResult: TN-Validation-Passed-A From: "John Smith" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
After the transformation
X-StirResult: TN-Validation-Passed-A From: "John Smith*" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
-
The second if_match transformation checks the following condition: if the call does not have an
Identity
header and because of that has the{{stir_verstat}}
set toNo-TN-Validation
, the rewrite_from_header_param transformation will replace the wholecnam
parameter of theFrom
header with thePOSSIBLE FRAUD
value, signifying that the call might be from an unwanted caller.Before the transformation
X-StirResult: No-TN-Validation- From: "John Smith" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
After the transformation
X-StirResult: No-TN-Validation- From: "POSSIBLE FRAUD" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
-
The last if_match transformation checks the following condition: if the call has an
Identity
header present but failed validation and the has the{{stir_verstat}}
set toTN-Validation-Failed
, such call is considered definitely unwanted and is rejected.Before the transformation
X-StirResult: TN-Validation-Failed- From: "John Smith" <sip:15162065613@12.7.193.174>;tag=as062a2e2a
The above transformations logic looks like the following:
Further Reading
Transformations API Reference
Refer to the Transformations API reference to get the complete list of their attributes and methods used with them:
- Transformations
- if_match
- lookup_rn
- reject
- rewrite_from
- rewrite_from_header_param
- rewrite_header
- rewrite_header_parameter
- rewrite_to
- rewrite_to_header_param
- set_header
- set_header_parameter
- set_user_data
- stir_validate
How It Works
Read the following articles to get a better understanding of how things work in CarrierX: