Cannot figure out how to get a working CRM template

Status
Not open for further replies.

perfect-touch

Customer
Joined
Mar 26, 2020
Messages
167
Reaction score
37
Now, I'm not known for being an idiot! I work in IT - and this week completed a Bash script which provides group voicemail functionality that we require in 3cx.

But for the life of me, I cannot work out how to implement a connection to our CRM (CapsuleCRM). This is perhaps the last thing standing in the way of me making the complete switchover from FreePBX to 3cx - at the moment all incoming calls are coming via FreePBX for queue hosting and CRM lookups.

Has anyone out there successfully created a CRM template for CapsuleCRM they'd be willing to share?
 
There's an API for CapsuleCRM: https://developer.capsulecrm.com/v2

Have you taken a look? It seems fairly easy. Using the CRM template wizard, you should be good to build that template.
 
I suspect the problem may be with the returned JSON from Capsule when performing a number search. I I curl with the appropriate header I get this:-


{
"parties":[
{
"id":81943750,
"owner":null,
"team":null,
"type":"person",
"about":null,
"firstName":null,
"lastName":"A Carrington Motors",
"jobTitle":null,
"createdAt":"2015-05-24T15:30:59Z",
"updatedAt":"2015-06-07T09:33:38Z",
"organisation":null,
"lastContactedAt":null,
"title":null,
"pictureURL":"https://facehub.appspot.com/default/person?text=A&size=100",
"phoneNumbers":[
{
"id":157792778,
"type":"Mobile",
"number":"07850 776 059"

}

],
"addresses":[
{
"id":157792779,
"type":"Home",
"city":"Waltham Abbey",
"country":null,
"street":"52 Cartersfield Road",
"state":"Essex",
"zip":null

},
{
"id":157792780,
"type":"Postal",
"city":null,
"country":null,
"street":null,
"state":null,
"zip":"EN9 1JD"

},
{
"id":159384892,
"type":"Postal",
"city":"Waltham Abbey",
"country":null,
"street":"52 Cartersfield Road",
"state":"Essex",
"zip":"EN9 1JD"

}

],
"emailAddresses":[
{
"id":157792777,
"type":null,
"address":"[email protected]"

}

],
"websites":[


]

},
{
"id":80935036,
"owner":null,
"team":null,
"type":"person",
"about":null,
"firstName":"Mr",
"lastName":"Alan Carrington",
"jobTitle":null,
"createdAt":"2015-05-10T14:43:57Z",
"updatedAt":"2015-06-07T09:34:11Z",
"organisation":null,
"lastContactedAt":null,
"title":null,
"pictureURL":"https://facehub.appspot.com/default/person?text=MA&size=100",
"phoneNumbers":[
{
"id":155935388,
"type":"Mobile",
"number":"07850 776 059"

}

],
"addresses":[
{
"id":155935389,
"type":"Postal",
"city":"Waltham Abbey",
"country":null,
"street":"52 Cartersfield Road",
"state":"Essex",
"zip":null

}

],
"emailAddresses":[
{
"id":155935387,
"type":null,
"address":"[email protected]"

}

],
"websites":[


]

}

]
}
 
I'm not sure that 3CX CRM processor is expecting an array of parties?

If it could retrieve the first party in the array and grab the party id, a subsequent lookup with that ID seems more usable:-


{ "party":{ "id":81943750,
"owner":null,
"team":null,
"type":"person",
"about":null,
"firstName":null,
"lastName":"A Carrington Motors",
"jobTitle":null,
"createdAt":"2015-05-24T15:30:59Z",
"updatedAt":"2015-06-07T09:33:38Z",
"organisation":null,
"lastContactedAt":null,
"title":null,
"pictureURL":"https://facehub.appspot.com/default/person?text=A&size=100",
"phoneNumbers":[ {
"id":157792778,
"type":"Mobile",
"number":"07850 776 059"

}

],
"addresses":[ {
"id":157792779,
"type":"Home",
"city":"Waltham Abbey",
"country":null,
"street":"52 Cartersfield Road",
"state":"Essex",
"zip":null

},
{
"id":157792780,
"type":"Postal",
"city":null,
"country":null,
"street":null,
"state":null,
"zip":"EN9 1JD"

},
{
"id":159384892,
"type":"Postal",
"city":"Waltham Abbey",
"country":null,
"street":"52 Cartersfield Road",
"state":"Essex",
"zip":"EN9 1JD"

}

],
"emailAddresses":[ {
"id":157792777,
"type":null,
"address":"[email protected]"

}

],
"websites":[


]

}
}
 
I just keep getting a stack like this:-

System.InvalidOperationException: Sequence contains no matching element
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
at CrmIntegration.ScenarioAuthHttpClientProvider.<PerformScenarioIfNeeded>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at CrmIntegration.ScenarioAuthHttpClientProvider.<Create>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at CrmIntegration.ScenarioProcessorBase.<ProcessScenario>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at CrmIntegration.ScenarioProcessorBase.<Execute>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at CrmIntegration.CrmProcessor.<GetContactInfoFromCrm>d__12.MoveNext()
 
The CapsuleCRM lookup in FreePBX didn't work - but that was a Perl script and simple for me to debug and fix. I assume there is no way I can use a script in CFD to lookup and add the caller name? I had a quick look but the caller name property seemed read-only? :(
 
Nope, CFD cannot do that.

@edossantos or @Constantinos Constantinou should be able to tell what the 3CX service expects.

My take on this: create a middleware script in PHP or such. Run the query in that script and make 3CX talk to that script directly. This way, you control how data is sent and received between the two.

I am working on this already for a customer to implement VINSolutions CRM and it's a pain.
 
It feels more painful than it should - the ability for 3cx to call a script and receive the required caller name as a return...the other thing i wondered is whether the caller lookup has already been performed against the contacts list by the time the CFD was executed (or launch script from a dummy IVR) and maybe use the call control API to add/update a contact
 
It feels more painful than it should - the ability for 3cx to call a script and receive the required caller name as a return...the other thing i wondered is whether the caller lookup has already been performed against the contacts list by the time the CFD was executed (or launch script from a dummy IVR) and maybe use the call control API to add/update a contact
The Contact Lookup is performed as soon as the call comes into the PBX, right before it rings anywhere. This way, everybody has the right Caller ID. The 3CX can extract those data from the JSON for sure, I am just not sure how to access arrays. I would try `[parties][0].firstName` and such in the variable assignments (use the wizard it's easier).

As for CFD or even Call Control API, you can't modify the CID with that. Also, I wouldn't recommend playing with Call Control, they don't like that.
 
3CX expects a full contact object for a lookup. So, you need a phone number, first/last name OR company name, and a contact URL (can be dummy but 3CX uses it to match contacts when updating infos).
 
I think your idea of creating a PHP "proxy" between 3cx and CapsuleCRM is my only option... :(
 
You should be able to get the nodes from that JSON. I don't see a problem with that. Maybe you're not configuring your template accordingly. For example, to get the FirstName you can use:
parties[0].firstName

System.InvalidOperationException: Sequence contains no matching element
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
at CrmIntegration.ScenarioAuthHttpClientProvider.<PerformScenarioIfNeeded>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at CrmIntegration.ScenarioAuthHttpClientProvider.<Create>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at CrmIntegration.ScenarioProcessorBase.<ProcessScenario>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at CrmIntegration.ScenarioProcessorBase.<Execute>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at CrmIntegration.CrmProcessor.<GetContactInfoFromCrm>d__12.MoveNext()
This error seems to be an issue with the template you created. It seems you defined a scenario based authentication, but the auth scenario doesn't exist...

Can you share the XML you created? That will help us understand the issue.
 
  • Love
Reactions: Evolute IT
I think your idea of creating a PHP "proxy" between 3cx and CapsuleCRM is my only option... :(
It's what I did and I did mine in a way that I can use for more than 1 CRM, so I can use it as proxy for any CRM that would not integrate well with 3CX.

It also allows me to watch the recordings and upload them (and transcribe) to the CRM. 3CX doesn't do that, but I did my own!
 
Both of you - thank you so much for your help so far...

edossantos - when I set authentication to basic (as I think it should be) - I get:-

System.Exception: Server returned a non successful status code - HTTPStatusCode=Unauthorized - Reason=Unauthorized - Content={ "message": "Requires authentication" }

When set to "scenario", authentication is successful - but the previous stack occurs...
 
You should be able to get the nodes from that JSON. I don't see a problem with that. Maybe you're not configuring your template accordingly. For example, to get the FirstName you can use:
parties[0].firstName


This error seems to be an issue with the template you created. It seems you defined a scenario based authentication, but the auth scenario doesn't exist...

Can you share the XML you created? That will help us understand the issue.

<?xml version="1.0"?>
<Crm xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Country="US" Name="Test" Version="1">
<Number Prefix="AsIs" MaxLength="[MaxLength]" />
<Connection MaxConcurrentRequests="2" />
<Parameters>
<Parameter Name="APIkey" Type="String" Parent="General Configuration" Editor="String" Title="API Key:" />
</Parameters>
<Authentication Type="Basic">
<Value>Authorization: Bearer [APIkey]</Value>
</Authentication>
<Scenarios>
<Scenario Id="" Type="REST">
<Request Url="https://api.capsulecrm.com/api/v2/parties/search?q=[Number]" MessagePasses="0" RequestEncoding="UrlEncoded" RequestType="Get" ResponseType="Json" />
<Rules>
<Rule Type="Any">parties[0].id</Rule>
</Rules>
<Variables>
<Variable Name="ContactID" Path="parties[0].id">
<Filter />
</Variable>
<Variable Name="FirstName" Path="parties[0].firstName">
<Filter />
</Variable>
<Variable Name="LastName" Path="parties[0].lastName">
<Filter />
</Variable>
<Variable Name="PhoneBusiness" Path="party.id">
<Filter />
</Variable>
</Variables>
<Outputs AllowEmpty="false">
<Output Type="ContactID" Passes="0" Value="[ContactID]" />
<Output Type="FirstName" Passes="0" Value="[FirstName]" />
<Output Type="LastName" Passes="0" Value="[LastName]" />
<Output Type="PhoneBusiness" Passes="0" Value="[PhoneBusiness]" />
<Output Type="ContactUrl" Passes="0" Value="https://api.capsulecrm.com/api/v2/parties/[ContactID]" />
</Outputs>
</Scenario>
</Scenarios>
</Crm>
 
If it stops you wasting your time edossantos, I'll happily create a temporary authorization token for the CRM API - I'm not using OAuth, just an API key
 
<Authentication Type="Basic">
<Value>Authorization: Bearer [APIkey]</Value>
</Authentication>
The Authorization header name will be automatically added, so you must not put it there. When using Basic authentication you should only set the value to:
Bearer [APIkey]

Try this to get the authorization working first. Then we move to the next issue.
 
  • Like
Reactions: Evolute IT
I still can't get authentication to work... I have amended the auth string to just Bearer [APIkey] but still get:-

System.Exception: Server returned a non successful status code - HTTPStatusCode=Unauthorized - Reason=Unauthorized - Content={ "message": "Requires authentication" }

I was thinking - the API key contains the '/' and '+' characters.. do these need escaping?
 
Checking your template, you need to change it as follows:
1) For the Rules, you need to specify only:
XML:
<Rules>
    <Rule Type="Any">parties.id</Rule>
</Rules>

This way 3CX can identify each record as an item from the result array.

2) The same applies to the variables you define, they will be taken from the proper item from the result array:
XML:
<Variable Name="ContactID" Path="parties.id" />
<Variable Name="FirstName" Path="parties.firstName" />
<Variable Name="LastName" Path="parties.lastName" />

3) The PhoneBusiness variable is taking the value from a wrong place, and you need to add a filter condition to it because the response has many phones in that phoneNumbers array, it should be:
XML:
<Variable Name="PhoneBusiness" Path="parties.phoneNumbers.number">
    <Filter>
        <Rule Type="Equals" Ethalon="Work">parties.phoneNumbers.type</Rule>
    </Filter>
</Variable>
<Variable Name="PhoneMobile" Path="parties.phoneNumbers.number">
    <Filter>
        <Rule Type="Equals" Ethalon="Mobile">parties.phoneNumbers.type</Rule>
    </Filter>
</Variable>
<Variable Name="PhoneHome" Path="parties.phoneNumbers.number">
    <Filter>
        <Rule Type="Equals" Ethalon="Home">parties.phoneNumbers.type</Rule>
    </Filter>
</Variable>
<Variable Name="PhoneDirect" Path="parties.phoneNumbers.number">
    <Filter>
        <Rule Type="Equals" Ethalon="Direct">parties.phoneNumbers.type</Rule>
    </Filter>
</Variable>
<Variable Name="FaxBusiness" Path="parties.phoneNumbers.number">
    <Filter>
        <Rule Type="Equals" Ethalon="Fax">parties.phoneNumbers.type</Rule>
    </Filter>
</Variable>
 
  • Like
Reactions: Evolute IT
Status
Not open for further replies.

Forum statistics

Threads
112,326
Messages
591,900
Members
165,479
Latest member
Senergia