CRM Integrations Guide

CRM Integrations Guide

ICTContact talks to a CRM in two separate directions, and each one lives on its own screen. You pull contacts out of a CRM into a contact group under CRM > Customer > Contact Groups. You push call results back into a CRM, or into any other web service, under Contact Center > Messages > Integrations. You can use either one on its own.

Direction 1: import contacts from a CRM into a Contact Group

Every contact group has a Group Type. The default is Internal Contacts, a plain list you fill from a CSV file or from another group. The other types are CRM feeds. When you pick one, the form shows that CRM's connection fields, and ICTContact fetches the records from the CRM itself.

  1. Go to CRM > Customer > Contact Groups and click Add new.
  2. Under Group Description, fill in Title and Description.
  3. Set Group Type to the CRM you want. The list has Internal Contacts plus the CRM entries your account has permission for (see the connector list below).
  4. Fill in the fieldset that appears for that CRM. The fields are the CRM's own API settings. Here are three examples from the code:
    • Zoho CRM Feed Configuration: Accounts URL (default https://accounts.zoho.com), API Base (default https://www.zohoapis.com/crm/v6), Client ID, Client Secret, Refresh Token, Module (Contacts or Leads), Page Size (max 200), Import Limit (0 = unlimited).
    • Pipedrive Feed Configuration: API Base URL (default https://api.pipedrive.com/api/v1), API Token, Import Limit (0 = unlimited), Page Size (default 100).
    • Salesforce Feed Configuration: Login URL, Consumer Key, Consumer Secret, Refresh Token, API Version, Object (Contact or Lead), Page Size (LIMIT), Import Limit (0 = unlimited).
      SuiteCRM asks for Base URL, Grant Type (Client Credentials or Password), Client ID, Client Secret, Username and Password (password grant only), Module, Page Size and Import Limit. Agile CRM asks for Subdomain, Login Email and API Key. Most connectors follow one of these two shapes: an OAuth refresh token, or a static API key.
  5. Click Save.

Two things catch people out here.

First, the Import Contacts (Optional) and Scan for CNAM sections disappear as soon as you choose a CRM type. That's expected. A CRM group is filled by the feed, not by a file.

Second, saving the group doesn't contact the CRM. The fetch runs when a campaign that uses this group starts. At that point the campaign daemon:

  1. calls the feed,
  2. pulls records page by page up to the Import Limit,
  3. normalises the phone numbers and writes the contacts,
  4. links each one to the CRM record in contact_link (remote id and module),
  5. puts them in the campaign spool.

So if the credentials are wrong, you'll find out in the campaign log, not on the group form. Check logs/campaign-<id>/daemon.log and logs/gui.log for lines starting with Creating instance of and Loading feed.

Group Type is locked after the first save. If you want to switch a group from Internal to a CRM feed, create a new group.

Lead Groups and Deal Groups don't offer CRM types. Their Group Type list has one entry (Internal Leads, Internal Deals).

Direction 2: push call results with an Integration

An Integration is a saved outbound request. The campaign daemon runs it at the end of a call and fills in tokens such as the contact's phone number and the call status. You create it once under Contact Center > Messages > Integrations, then attach it to a campaign, a feedback option or an IVR node.

The list shows Integration Name, Type, Description and Operations (Edit). The top buttons are search, New Integration and Refresh. The form has Save, Reset and Cancel. Delete Selected removes the rows you've ticked. The search panel filters on Integration, Integration Type and Date.

Creating an integration

  1. Click New Integration.
  2. Under Integration Info, enter Integration Name (required), choose Integration Type and add a Description if you like.
  3. Fill in the fieldset for the type you chose (details below).
  4. Click Save.

You can't change Integration Type after saving. The field is hidden on the edit screen. If you picked the wrong type, create a new record.

The three built-in types

SOAP WSDL API. Integration Configuration has WSDL URL, SOAP Action, and nine Parameter# rows (Name / Value). In each row, type the parameter name, then either pick a token from the value dropdown or choose Custom value and type your own text. The Show available tokens link lists what you can insert. Request parameters and the SOAP reply are written to logs/gui.log as Soap request parameters: and Soap results:. A SOAP fault is logged as Soap error!.

Custom Http request. Fields: Remote URL, Http Method (GET, POST, PUT, DELETE), Request data, Request headers, Response encoding (Plain or JSON Encoded). The insert token button next to Remote URL and Request data drops a token at the cursor. Request data is only sent for POST and PUT. Put one header per line in Request headers, for example Content-Type: application/json. Response encoding is stored, but the current code doesn't apply it. The reply is logged as text either way. For every run you'll see Http request url:, Http request body: and Http results: in logs/gui.log.

Queue for callback. Callback Configuration has Callback Campaign (a dropdown of your campaigns), Schedule After and Expiry After (both duration pickers). When it runs, the contact is inserted into the selected campaign as a new spool entry. If the call came through an agent, the new entry keeps the same extension. It's handy for "call me back later" from a survey or an IVR key press. The log line is Callback saved as spool_id:.

The CRM connectors

These are the connector types registered in integration.php on the current build, in the order they appear in the Integration Type list: Agile CRM, ActiveCampaign, Apptivo CRM, Capsule CRM, Close CRM, Copper CRM, Creatio CRM, Dynamics 365, Freshsales, Keap (Infusionsoft), Less Annoying CRM, Monday.com, Streak CRM, YetiForce CRM, Zendesk Sell, Vtiger CRM, SuiteCRM, Salesforce, Zoho CRM, SugarCRM, Pipedrive. Each entry only appears if your user has the matching permission, so a tenant may see fewer than 21.

Each connector shows a fieldset named " Configuration". The fields are the CRM's API credentials plus a module choice. Some examples:

Connector Fields
Zoho CRM Accounts URL, API Base, Client ID, Client Secret, Refresh Token, Module (Contacts / Leads)
Salesforce Login URL, Consumer Key, Consumer Secret, Refresh Token, API Version, Default Object (Contact / Lead)
Pipedrive API Base URL, API Token, Custom Status Field Hash (optional), Create Pipedrive Activity for each call (Yes / No)
SuiteCRM Base URL, Grant Type, Client ID, Client Secret, Username and Password (password grant only), Default Module (Contacts / Leads)
Agile CRM Subdomain, Login Email, API Key

Here's what a CRM connector does on each call, in order:

  1. It receives the contact id, campaign id, spool id, call status and disposition id from the daemon.
  2. It reads the local contact (name, phone, email), the campaign name, the agent, and the disposition with its comments, call duration and recording URL.
  3. It looks for the CRM record using the remote id already stored in contact_link (set when the contact was imported from that CRM). If there isn't one, it searches by phone, then by email.
  4. If nothing matches, it creates the record.
  5. It updates the record and adds a note or activity carrying the call result.
  6. It stores the new remote id back in contact_link, so the next call doesn't have to search again.

Disposition mapping. Before sending, the connector translates the ICTContact disposition name through the crm_disposition_map table (scope integration, keyed by the integration id). If there's no mapping row, the ICTContact name is sent unchanged. The current build has no screen for this table. The mapping is maintained in the database. Pipedrive also needs the Custom Status Field Hash filled in before it writes the mapped value to a custom field. Leave it blank and only the activity is created.

Connector log lines go to logs/gui.log, prefixed with the type in capitals, for example [ZOHOCRMHTTP] authenticate FAILED http=401.

Integrations > New Integration with Integration Type set to Salesforce and the Salesforce Configuration fieldset visible” width=”1600″ loading=”lazy” style=”max-width:100%;height:auto;border:1px solid #e2e8f0;border-radius:6px;” /></figure>
<h2>Where an integration is attached</h2>
<p>The Integration list only stores the request. Nothing runs until a campaign or IVR refers to it.</p>
<p><strong>Integration Campaign</strong> (Contact Center > Campaigns Management > Add, card "Integration Campaign" under Voice Broadcasting). This campaign type plays recorded offers and sends the recipient's response to your API. Its wizard has:</p>
<ul>
<li>Integration Offers: the number of offers, then for each offer an ID, Introduction and Detail recording.</li>
<li>The general messages: Greeting message, Offer end message (main menu), Detail end message (sub menu), Confirmation message, Thanks message, Wrong message.</li>
<li>The fieldset Integration APIs, with one required field, Integration, where you pick the saved record.</li>
</ul>
<p>Dial method is fixed to automatic for this type.</p>
<p><strong>Feedback Campaign</strong> (SMS). Each feedback option row has Description, Filter Keywords and an Action / Integration dropdown. When an inbound reply matches the keywords, that integration runs.</p>
<p><strong>IVR node "Integration API"</strong> (Contact Center > Messages > IVR, designer toolbox). The node's only setting is Integration. It fires when a call reaches the node, either through an IVR Campaign using that IVR or through a DID whose DID Forwarding target is that IVR.</p>
<p>On this build the generic campaign wizard has no integration field of its own (the old "Request (Call End)" step is disabled in the code). The Disposition form has no integration fields either. If you want a result pushed after an agent call, route the campaign through an IVR with an Integration API node, or use an Integration Campaign.</p>
<h2>Troubleshooting</h2>
<ul>
<li><strong>Nothing arrives at the CRM.</strong> First confirm the integration is attached somewhere (see above). Then open <code>logs/gui.log</code> and search for the integration's request lines. If there aren't any, the daemon never called it. If there are, the reply body is logged right after, and it usually names the problem (401 for bad credentials, 404 for a wrong base URL).</li>
<li><strong>OAuth connectors log <code>authenticate FAILED</code>.</strong> This happens on Zoho, Salesforce, SuiteCRM, Dynamics 365 and others using a refresh token when that token has been revoked. Mint a new one in the CRM and paste it into the integration.</li>
<li><strong>A CRM feed group stays at zero contacts.</strong> The pull runs when a campaign starts, not when you save. Start a campaign with that group and read <code>logs/campaign-<id>/daemon.log</code>.</li>
<li><strong>The disposition shows in the CRM under the ICTContact name.</strong> That's the default. Add rows to <code>crm_disposition_map</code> if the CRM needs its own values.</li>
<li><strong>Custom Http request with POST sends nothing.</strong> Request data is only used for POST and PUT. Check the Http Method, and check that the body uses tokens the insert token list knows.</li>
<li><strong>The integration runs but the row isn't found in the CRM.</strong> The lookup goes by stored remote id, then phone, then email. Phone numbers are compared after stripping everything except digits. So if a CRM stores numbers with a different country prefix, they won't match, and a new record is created instead.</li>
</ul>
<h2>Open source tools</h2>
<p>ICT Innovations also publishes an n8n node for ICTContact, the ICTAgent Chrome extension and tvbench as open source at github.com/ictinnovations. They connect to this same REST and integration surface, and they're maintained separately from the product.</p>
					</div>

				
				</article>

			

			</div>

					</div>
	</div>


</div>

	<footer class=