Skip to content

findContact (by Phone)

This interface is central to App Connect's framework as it is responsible for matching phone numbers with contacts in the target CRM. This interface powers the following key features:

  • call pop
  • call logging
  • sms logging

This interface can return zero, one or more contacts. If multiple contacts are returned, App Connect will prompt the end user to select the specific contact to be used when logging calls.

If no contact is found, do not create a contact in its place. When logging calls, if no contacts are found associated with a phone number, then the framework to prompt the user to create a contact. The user will enter a name, and then call the createContact interface, and then call the createCallLog with the newly created contact ID.

This interface is called in the following circumstances:

  • When a call is received.
  • When a user manually clicks the "refresh contact" action for a contact or phone call.
  • When a user accesses App Connect the first time in an attempt to perform an initial contact match operation for recent phone calls.

Manually refresh contact

The "Refresh contact" action in App Connect's contact list

Request parameters

Parameter Description
user An object describing the Chrome extension user associated with the action that triggered this interface.
authHeader The HTTP Authorization header to be transmitted with the API request to the target CRM.
phoneNumber The phone number to search for within the target CRM, provided in an E.164 format, e.g. +11231231234.
overridingFormat (Optional) If defined by the user under advanced settings, this will contain alternative formats the user may wish to use when searching for the phoneNumber

Alternative formats

Some CRM's have very restrictive APIs with regards to searching for phone numbers, meaning they require an exact match in order to find a contact with that phone number. To work around this restriction, users are allowed to specify a list of phone number formats which they often use when entering phone numbers into the CRM. It is the intention that each adapter when provided a list of overridingFormat values to convert the E.164 phone number into each of the overriding formats, and to search for each one until a contact is found.

Remember: only a single call the getContact interface will be made. The developer is responsible for searching for each alternative format.

Return value(s)

This interface returns a single object. That object describes the contacts that were found. It has following properties:

Parameter Description
matchedContactInfo An array of objects containing id, name and optionally additionalInfo and isNewContact.
returnMessage message, messageType and ttl

isNewContact is only used as an extra option in contact list for users to be able to create new contacts

Returning contact specific information

In some circumstances when a call is being logged you need to collect contact or account specific information from the agent logging the call. Consider for a moment a use case you can see implemented in our Clio adapter in which you want to link or associate a phone call with a specific legal matter. You don't know the list of possible matters until you have successfully matched the phone call with a contact. Then you want to present the agent with a easy-to-use pull-down menu showing the list of matters associated with the contact.

To do this you need to do two things. First, in your manifest, you want to define the field you want to collect from the agent. On this field you will be sure to set contactDependent to true.

"page": {
    "callLog": {
        "additionalFields": [
            {
                "const": "matters",
                "title": "Matter",
                "type": "selection",
                "contactDependent": true
            }
        ]
    }
}

Then in your adapter, when you return your list of contacts, for each contact you will return the additionalInfo property in which you provide the list of matters.

[{ 
    "const": m.matter.id, 
    "title": m.matter.display_number, 
    "description": m.matter.description, 
    "status": m.matter.status 
}]

The values returns are bound to the field via correlating the two const values found in the additional field and the contact record.

Example response

{
  matchedContactInfo:[
    {
      id: 'contact id',
      name: 'John Doe',
      additionalInfo: null,
      isNewContact: false
    },
    {
        id: 'createNewContact',
        name: 'Create new contact...',
        additionalInfo: null,
        isNewContact: true
    }
  ],
  returnMessage:{
    message: 'Found 1 contact',
    messageType: 'warning', // 'success', 'warning' or 'danger'
    ttl: 30000 // in miliseconds
  }
}

Reference

    const numberToQueryArray = [];
    if (isExtension) {
        numberToQueryArray.push(phoneNumber);
    }
    else {
        numberToQueryArray.push(phoneNumber.replace(' ', '+'));
    }
    // You can use parsePhoneNumber functions to further parse the phone number
    const matchedContactInfo = [];
    // for (var numberToQuery of numberToQueryArray) {
    //     const personInfo = await axios.get(
    //         `https://api.crm.com/contacts?query=number:${numberToQuery}`,
    //         {
    //             headers: { 'Authorization': authHeader }
    //         });
    //     if (personInfo.data.length > 0) {
    //         for (var result of personInfo.data) {
    //             foundContacts.push({
    //                 id: result.id,
    //                 name: result.name,
    //                 type: result.type,
    //                 phone: numberToQuery,
    //                 additionalInfo: null
    //             })
    //         }
    //     }
    // }
    if (mockContact != null) {
        matchedContactInfo.push(mockContact);
    }
    console.log(`found contacts... \n\n${JSON.stringify(matchedContactInfo, null, 2)}`);

    // If you want to support creating a new contact from the extension, below placeholder contact should be used
    matchedContactInfo.push({
        id: 'createNewContact',
        name: 'Create new contact...',
        additionalInfo: null,
        isNewContact: true
    });
    //-----------------------------------------------------
    //---CHECK.3: In console, if contact info is printed---
    //-----------------------------------------------------
    return {
        successful: true,
        matchedContactInfo,
        returnMessage: {
            messageType: 'success',
            message: 'Successfully found contact.',
            detaisl: [
                {
                    title: 'Details',
                    items: [
                        {
                            id: '1',
                            type: 'text',
                            text: `Found ${matchedContactInfo.length} contacts`
                        }
                    ]
                }
            ],
            ttl: 3000
        }
    };  //[{id, name, phone, additionalInfo}]
}

// - contactInfo: { id, type, phoneNumber, name }
// - callLog: same as in https://developers.ringcentral.com/api-reference/Call-Log/readUserCallRecord
// - note: note submitted by user
// - additionalSubmission: all additional fields that are setup in manifest under call log page
async function createCallLog({ user, contactInfo, authHeader, callLog, note, additionalSubmission, aiNote, transcript, composedLogDetails }) {
    // ------------------------------------
    let extraDataTracking = {};
    phoneNumber = phoneNumber.replace(' ', '+')
    // without + is an extension, we don't want to search for that
    if (!phoneNumber.includes('+')) {
        return {
            matchedContactInfo: null,
            returnMessage: {
                message: 'Logging against internal extension number is not supported.',
                messageType: 'warning',
                ttl: 3000
            }
        };
    }
    const phoneNumberObj = parsePhoneNumber(phoneNumber);
    let phoneNumberWithoutCountryCode = phoneNumber;
    if (phoneNumberObj.valid) {
        phoneNumberWithoutCountryCode = phoneNumberObj.number.significant;
    }
    const personInfo = await axios.get(
        `https://${user.hostname}/api/v2/persons/search?term=${phoneNumberWithoutCountryCode}&fields=phone`,
        {
            headers: { 'Authorization': authHeader }
        });
    extraDataTracking = {
        ratelimitRemaining: personInfo.headers['x-ratelimit-remaining'],
        ratelimitAmount: personInfo.headers['x-ratelimit-limit'],
        ratelimitReset: personInfo.headers['x-ratelimit-reset']
    };
    const matchedContactInfo = [];
    for (const person of personInfo.data.data.items) {
        const dealsResponse = await axios.get(
            `https://${user.hostname}/api/v2/deals?person_id=${person.item.id}&&status=open`,
            {
                headers: { 'Authorization': authHeader }
            });
        extraDataTracking = {
            ratelimitRemaining: dealsResponse.headers['x-ratelimit-remaining'],
            ratelimitAmount: dealsResponse.headers['x-ratelimit-limit'],
            ratelimitReset: dealsResponse.headers['x-ratelimit-reset']
        };
        const relatedDeals = dealsResponse.data.data ?
            dealsResponse.data.data.map(d => { return { const: d.id, title: d.title } })
            : null;
        let leadsResponse = null;
        try {
            leadsResponse = await axios.get(
                `https://${user.hostname}/v1/leads?person_id=${person.item.id}`,
                {
                    headers: { 'Authorization': authHeader }
                });
            extraDataTracking = {
                ratelimitRemaining: leadsResponse.headers['x-ratelimit-remaining'],
                ratelimitAmount: leadsResponse.headers['x-ratelimit-limit'],
                ratelimitReset: leadsResponse.headers['x-ratelimit-reset']
            };
        }
        catch (e) { leadsResponse = null; }
        const relatedLeads = leadsResponse?.data?.data ?
            leadsResponse.data.data.map(l => { return { const: l.id, title: l.title } })
            : null;
        matchedContactInfo.push(formatContact(person.item, relatedDeals, relatedLeads));
    }
    matchedContactInfo.push({
        id: 'createNewContact',
        name: 'Create new contact...',
        isNewContact: true
    });
    return {
        successful: true,
        matchedContactInfo,
        extraDataTracking
    };
}
async function findContactWithName({ user, authHeader, name }) {