Useful tips for 3CX API scripts

Status
Not open for further replies.

atommo

Platinum Partner
Joined
Jul 9, 2024
Messages
14
Reaction score
10
In the last few weeks I have learned a lot from this community, and as a way to give back I wanted to give some concise, hopefully invaluable points to help with the understanding of how things work.

Escape characters
First off, we have escape characters. Typically in an API call, you might use the + and & and ' symbols in request URLs. You will need to escape these.

'+' is %2B
'
is %27
& is &
= is %3D

If you don't do this, then when you send the URL request containing the unescaped characters it will likely error (this is due to REST calls typically being sent to a SQL database backend, and SQL will treat the unescaped characters like commands rather than search criteria).

One call to find contact ID, another call to retrieve that contact's details
Another issue I ran into was finding the CRM template generator had a limitation of its wizard, where it only worked for APIs where the name details were returned from a number search.

In a lot of CRMs, the name details are returned from a separate call. For example:

Code:
https://api.example.com/telephones?appid=123&appsecret=456&filter=telNo%3D%27[Number]%27

The result might only return a 'contactid' but no name, so you would need to do a subsequent search to get the name.

The trick here is to have this near the end of the scenario:
<Outputs Next="[NextScenarioName]" AllowEmpty="false" /> //After finishing this scenario, it will run the '[NextScenarioName]' scenario next. The 'AllowEmpty'=False means it won't run the next scenario if it didn't get a result from the scenario that just finished running.



An example generic script

XML:
<?xml version="1.0"?>
<Crm xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Country="US" Name="Example" Version="1" SupportsEmojis="true">
  <Number Prefix="AsIs" MaxLength="-1" /> //The 'MaxLength' being set as '-1' means there is no limit to the length of the number it will search. If this was '5' for example, it would only search for the final 5 digits of the number, cutting off anything to the left.
 
 
  <Connection MaxConcurrentRequests="2" /> //Means it cannot do more than 2 API call lookups simultaneously.
 
 
  <Parameters />
  <Authentication Type="No" /> //This is because the authentication is basic (included in the URL)

//If authentication was more advanced, you would need to amend this. For example:
 <Authentication Type="Basic">
    <Value>username:password</Value>
  </Authentication>
 
 
  <Scenarios> //There are two scenarios- the first gets the contact ID, the second uses the contact ID to get the firstname and lastname.
 
 
    <Scenario Id="" Type="REST" EntityId="Contacts" EntityOrder=""> //This is the first scenario.
      <Request SkipIf="" Url="https://api.example.com/telephones?appid=123&amp;appsecret=456&amp;filter=telNo%3D%27[Number]%27" MessagePasses="0" RequestEncoding="UrlEncoded" RequestType="Get" ResponseType="Json" />
      <Rules>
        <Rule Type="Any">data.contactid</Rule> //The rule should always have 'data.[variable]' within, like this. It needs to be a path.
      </Rules>
      <Variables>
              <Variable Name="ContactId" LookupValue="" Path="">data.contactid<Filter /></Variable> //This created a variable called 'ContactId' and populates it with the value returned from 'data.contactid' from the API call.
      </Variables>
      <Outputs Next="GetContactById" AllowEmpty="false" /> //After finishing this scenario, it will run the 'GetContactById' scenario next.
    </Scenario>
 
 
    <Scenario Id="GetContactById" Type="REST" EntityId="" EntityOrder=""> //This is the 'GetContactById' scenario.
      <Request SkipIf="" Url="https://api.example.com/telephones?appid=123&amp;appsecret=456&amp;filter=contactid%3D%27[ContactId]%27" MessagePasses="0" RequestEncoding="UrlEncoded" RequestType="Get" ResponseType="Json" />
      //The 'SkipIf' means it won't run this scenario if the 'ContactId' variable is empty. If the variable is empty, it indicates no matching contact was found against the calling number.
 
      <Rules>
        <Rule Type="Any">data.contactid</Rule> //Again, this rule checks for the existence of this variable being returned. If this doesn't return, the scenario as a whole will fail.
      </Rules>
      <Variables>
        <Variable Name="ContactId" Path="">data.contactid<Filter /></Variable>
        <Variable Name="FirstName" Path="">data.firstName<Filter /></Variable>
        <Variable Name="LastName" Path="">data.lastName<Filter /></Variable>
      </Variables> //The above three are fairly self-explanatory.
      <Outputs AllowEmpty="false">
        <Output Type="ContactID" Passes="0" Value="[ContactId]" />
        <Output Type="ContactUrl" Passes="0" Value="[ContactId]" />
        <Output Type="FirstName" Passes="0" Value="[FirstName]" />
        <Output Type="LastName" Passes="0" Value="[LastName]" />
        <Output Type="EntityId" Passes="0" Value="[ContactId]" />
        <Output Type="PhoneBusiness" Value="[Number]"/>
        <Output Type="EntityType" Passes="0" Value="Contacts" />
      </Outputs>
      //These 'output' types are mandatory for 3CX in order to return a successful response. If you don't need to use them for any reason, they still need to be included so the scenario doesn't fail.
      //Anything in [these] is considered a variable. Therefore, Value="[cheeseburger]" would be whatever the value in the variable 'cheeseburger' would be.
      //The value of 'cheeseburger' would be set in the 'variables' section like you see values are set for 'ContactId', 'FirstName' and 'LastName'.
 
    </Scenario>
  </Scenarios>
</Crm>


Comments on the above example
Data paths follow the data.object convention. However, if an object is nested in multiple parent objects, you can omit the 'data' root. For example, if you had data.telNo.numberOwner.personName.legalFirstName then you could just have telNo.numberOwner.personName.legalFirstName as the path instead (3CX will be able to work out that 'data' is implied at the beginning)..

This will not work if you are only doing a single-level result however, so putting 'FirstName' instead of 'data.FirstName' would just give an error.



Arrays
If for any reason, you are working with a result that returns as an array, the trick to deal with it is (many thanks to @edossantos_sipcaller who guided me on this):

If you want to get the ID from that JSON response, you could use for example this:
XML:
        <Variable Name="TelephoneId" Path="telephoneNumbers.id">
          <Filter>
            <Rule Type="Equals" Ethalon="TelephoneNumber">telephoneNumbers.entityType</Rule>
          </Filter>
        </Variable>

That will get the value from the id node when the entityType node has the value "TelephoneNumber". However, if more than 1 children items met this condition, the matching will fail. It must be unique. And from what you sent as a sample response, that entityType is repeated in many sub items.

In other words, if an array is returned then you need to filter it by a different variable from the one you want to take as an output.

See example:

XML:
<Rules>
        <Rule Type="Any">telNos.id</Rule> //Want to take the 'id' value as the output from the scenario.
      </Rules>
      <Variables>
              <Variable Name="ContactId" Path="telNos.id"><Filter>
                <Rule Type="Equals" Ethalon="TelNo">telNos.entityType</Rule> //Set the rule to filter by any 'entityType' where the value matched 'TelNo'.
              </Filter>
              </Variable>
      </Variables>
I honestly still don't fully understand this particular concept so someone else may be able to explain better, but you can always take that snippet of code to experiment with and maybe you can get a better understanding that way.
 

Attachments

  • 3cx templates.png
    3cx templates.png
    40.9 KB · Views: 14
Last edited:
Further recommendatons
If you go on your 3CX server, find this area:
View attachment 43088
There are some baked-in templates which you can look at for more understanding of how they are coded. I referred to the Bitrix and PipeDrive ones for my own projects. Select one from the list, then click 'show template'. You can then copy the code out to a notepad and play around with it. If you want to fully get into this sort of development, it would be worth looking over the others too.


Some final tips are these:

- Always look over the API documentation for whatever CRM you are looking to integrate with! Some have better documentation than others- there will always be a period of trial and error as you get a feel for each system and its layout of how data is stored.

- Ask for access to an API sandbox for the platform! Each CRM should have an API sandbox (as well as the respective full interface which a typical user would see) so you can figure out how it all fits together. Some may even have more than one sandbox- such as one with full permissions, and another with restricted permissions so you can test your app has the right access level.

- If the CRM development area doesn't include a try-it-yourself API call area, use a REST application! I used Postman to test a few API calls for example. Usually the CRM will provide you with some example API calls which you can use with the REST client of your choice, as a starting point.

- Make full use of the CRM's API support team! Most CRMs want to encourage developers to make API applications for their product, since more integration with other systems makes it attractive for more customers. Don't be afraid to ask for help!

And finally... Make use of the 3CX forum! I wouldn't have been able to come this far without the help of this forum.

Special kudos to @edossantos_sipcaller and @ConceptsWeb for your help with my issues.

Hopefully anyone reading found this useful.
 
Last edited:
Yet something else I've thought of.

Sometimes the 3CX CRM template generator program won't give you an accurate result when testing your script, especially if it has complexity.

The best thing to do if you aren't sure is upload your template to your 3CX server, in the 'integrations' section (add template button) and then use the 'test' functionality.

This will be able to give you much more detailed results of what is happening, such as if one of your scenarios is finishing without a value.

I have found on some occasions, that where the template doesn't seem to work when testing it with the template generator program, it works without issue when testing on the 3CX server itself.
 
Yet something else I've thought of.

Sometimes the 3CX CRM template generator program won't give you an accurate result when testing your script, especially if it has complexity.

The best thing to do if you aren't sure is upload your template to your 3CX server, in the 'integrations' section (add template button) and then use the 'test' functionality.

This will be able to give you much more detailed results of what is happening, such as if one of your scenarios is finishing without a value.

I have found on some occasions, that where the template doesn't seem to work when testing it with the template generator program, it works without issue when testing on the 3CX server itself.

I wonder who built this CRM template generator. :rolleyes: It must be someone old, very old (Running gang, he's definitely going to react xD).

It's possible that the PBX, now on a new version (v20), supports scenarios that were not previously supported.

Unless I'm mistaken, the CRM template generator has not been updated since the release of v20.
 
@atommo, thanks for sharing this valuable information!

@Guillaume Bourgeois, xDxD, I bet he's not that old! xDxD And I think he left a new version ready for v20, so it's probably going to be released soon!
 
Status
Not open for further replies.