15 min.

How to use Zoho CRM API with PHP

Create Leads and Deals

Learn to effectively manage leads, deals, accounts, and contacts with custom PHP service methods. Our step-by-step instructions cover everything from creating basic deals and handling file attachments to dynamically linking contacts and accounts

Before diving into the manual, you can clone a ready-made boilerplate implementation for Zoho CRM integration for Laravel and Symfony.

 

https://github.com/blowstack/zoho-crm-laravel.git

https://github.com/blowstack/zoho-crm-symfony.git

 

 

To effectively utilize this article, you should have already implemented a service that manages the authentication process. I discuss how to accomplish this in Authenticate Zoho CRM in PHP article, including code snippets at the end. Each Zoho API call requires a configuration and an access token; therefore, utilizing this service simplifies the process for you. Moreover, it facilitates the implementation of numerous solutions within this service (specifically, ZohoCRM).

 

 

Creating a Form and Controller for Lead Generation

 

For lead generation purposes, I created a contact form to collect basic data. Following this, you'll find a controller and a view. These components are implemented separately in both Symfony and Laravel, a common approach throughout this post. The same code is consistently applied within these frameworks, specifically within the ZohoCRM service.

 

Symfony Implementation

 

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ContactFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', TextType::class)
            ->add('lastName', TextType::class)
            ->add('company', TextType::class ,['required' => false])
            ->add('email', EmailType::class)
            ->add('phone', TextType::class)
            ->add('content', TextareaType::class)
            ->add('save', SubmitType::class)
        ;
    }
}

 

 

Next, create a controller to manage the form and its view:

 

 

<?php

namespace App\Controller;

use App\Service\ZohoCRM;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Form\ContactFormType;

class ContactController extends AbstractController
{
    /**
     * @Route("/contact", name="contact")
     * @param Request $request
     * @param ZohoCRM $zohoCRM
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function index(Request $request, ZohoCRM $zohoCRM)
    {
        $ContactForm = $this->createForm(ContactFormType::class);

        $ContactForm->handleRequest($request);

        if($ContactForm->isSubmitted() &amp;amp;amp; $ContactForm->isValid()) {

            $data = $ContactForm->getData();
            $zohoCRM->createLead(
                $data['firstName'],
                $data['lastName'],
                $data['email'],
                $data['content'],
                $data['company'],
                $data['phone']
            );
        }

        return $this->render('contact/index.html.twig', [
            'Form' => $ContactForm->createView(),
        ]);
    }
}

 

 

Template for the contact form (templates/contact/index.html.twig):

 

 

// templates/contact/index.html

{% extends 'base.html.twig' %}

{% block body %}

    <div class="container">
        <div class="row justify-content-center">
            <div class="col-sm-10 col-lg-4 text-center text-white">
                <h1 class="mt-5">Contact</h1>
                {{ form_start(Form) }}

                <div class="form-group">
                {{ form_label(Form.firstName) }}
                {{ form_widget(Form.firstName, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_label(Form.lastName) }}
                    {{ form_widget(Form.lastName, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_label(Form.company) }}
                    {{ form_widget(Form.company, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_label(Form.email) }}
                    {{ form_widget(Form.email, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_label(Form.phone) }}
                    {{ form_widget(Form.phone, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_label(Form.content) }}
                    {{ form_widget(Form.content, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_widget(Form.save, {'attr': {'class': 'btn btn-primary'}, 'label': 'Send message'}) }}
                </div>
                {{ form_end(Form) }}
            </div>
        </div>
    </div>
{% endblock %}

 

 

Create a Basic Lead

 

There are eight basic and most popular fields a lead can consist of, with the first name and last name being obligatory. Below is a simple implementation that covers these eight basic fields to be passed to the CRM. To accomplish this, let's add a new method in the service.

 

 

ZohoCRM Service (src/Service/ZohoCRM)

 

 

...
public function createLead($firstName, $lastName, $email, $description = null, $company = null, $phone = null) 
{
        $RecordLead = ZCRMRecord::getInstance("Leads",null);
        $RecordLead->setFieldValue('First_Name', $firstName);
        $RecordLead->setFieldValue('Last_Name', $lastName);
        $RecordLead->setFieldValue('Email', $email);
        $RecordLead->setFieldValue('Company', $company);
        $RecordLead->setFieldValue('Phone', $phone);
        $RecordLead->setFieldValue('Description', $description);
        $RecordLead->setFieldValue('Lead_Source', 'contact form');

        $Leads = ZCRMModule::getInstance('Leads');

        $arrayOfLeads = [];
        array_push($arrayOfLeads, $RecordLead);

        try {
            $response = $Leads->createRecords($arrayOfLeads);
            $result = $response->getData()[0]->getEntityId();
        }
        catch (Exception $exception) {
            // this is not proper way to handle exceptions on the production env
            return $exception;
        }
    }
    ..
    }

 

 

 

Get all possible lead fields

 

Wondering about the other fields beyond the basic ones? Discovering all the fields that can be passed to Zoho CRM is straightforward. Simply query the CRM with your first inserted lead. To extend the utility, we'll introduce a function that allows searching for records from any module, not limited to leads.

 

 

ZohoCRM Service

 

In your ZohoCRM service file located at src/Service/ZohoCRM.php, add the following function to perform searches:

 

 

...

function getRecords($moduleName, $email) {

    $Module = ZCRMModule::getInstance($moduleName);
    return $Module->searchRecordsByEmail($email);
}
...

 

 

This function leverages email for searching, but Zoho CRM provides multiple methods for record search, including:

 

  • searchRecordsByPhone($phone)
  • searchRecordsByWord($word)
  • searchRecordsByCriteria($criteria)
  •  

The searchRecordsByCriteria method is particularly flexible, allowing searches by any field (e.g., last name with ['Last_Name' => $lastName]) or combinations thereof.

 

To review the search response, implement a new ZohoController and a corresponding Twig template. Pass the lead's email address via the URL to initiate the search.

 

Symfony Implementation

 

Create a controller in src/Controller/admin/ZohoController.php:

 

 

<?php

namespace App\Controller\admin;

use App\Service\ZohoCRM;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ZohoController extends AbstractController
{
    /**
     * @Route("/admin/zoho/lead/{email}", name="admin_zoho_lead")
     * @param ZohoCRM $zohoCRM
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function index(ZohoCRM $zohoCRM, $email)
    {

        $lead = $zohoCRM->getRecords('Leads', $email);

        return $this->render('admin/lead.html.twig', [
            'lead' => $lead,
        ]);
    }
}

 

 

And the Twig template templates/admin/lead.html.twig:

 

 

// templates/admin/lead.html.twig

{% extends 'base.html.twig' %}

{% block title %}Lead Details{% endblock %}

{% block body %}
    <h2>Lead Information</h2>
    {{ dump(lead) }}
{% endblock %}

 

 

This function and template setup will display the structure of the response from Zoho CRM, showing all available fields in a JSON format, which can be invaluable for understanding the data model and planning further integrations.

 

 

Handling the Response

 

The response object, BulkAPIResponse, encapsulates the data in a format where you can access all fields that can be sent to Zoho CRM. Note that this method can return multiple records if the search criteria are met. The structure includes a data array with detailed fields for each record.

 

 

zcrmsdk\crm\api\response\BulkAPIResponse {#413 ▼
  -bulkData: array:1 [▶]
  -status: null
  -info: zcrmsdk\crm\api\response\ResponseInfo {#412 ▶}
  -bulkEntitiesResponse: []
  -httpStatusCode: 200
  -responseJSON: array:2 [▼
    "data" => array:1 [▼
      0 => array:55 [▼
        "Owner" => array:3 [▶]
        "Company" => "BlowStack"
        "Email" => "b.stack@blowstack.com"
        "$currency_symbol" => "$"
        "Visitor_Score" => null
        "Last_Activity_Time" => null
        "Industry" => null
        "$state" => "save"
        "$converted" => false
        "$process_flow" => false
        "Street" => null
        "Zip_Code" => null
        "id" => "3652704000000955001"
        "$approved" => true
        "$approval" => array:4 [▶]
        "First_Visited_URL" => null
        "Days_Visited" => null
        "Created_Time" => "2020-07-15T21:22:26+02:00"
        "$editable" => true
        "City" => null
        "No_of_Employees" => null
        "State" => null
        "Country" => null
        "Last_Visited_Time" => null
        "Created_By" => array:3 [▶]
        "Annual_Revenue" => null
        "Secondary_Email" => null
        "Description" => "This is a message you should read."
        "Number_Of_Chats" => null
        "Rating" => null
        "$review_process" => array:3 [▶]
        "Website" => null
        "Twitter" => null
        "Average_Time_Spent_Minutes" => null
        "Salutation" => null
        "First_Name" => "Blow"
        "Full_Name" => "Blow Stack"
        "Lead_Status" => null
        "Record_Image" => null
        "Modified_By" => array:3 [▶]
        "$review" => null
        "Skype_ID" => null
        "Phone" => "+48483232323"
        "Email_Opt_Out" => false
        "Designation" => null
        "Modified_Time" => "2020-07-15T21:22:26+02:00"
        "$converted_detail" => []
        "Mobile" => null
        "$orchestration" => null
        "First_Visited_Time" => null
        "Last_Name" => "Stack"
        "$in_merge" => false
        "Referrer" => null
        "Lead_Source" => "contact form"
        "Fax" => null
      ]
    ]
    "info" => array:4 [▶]
  ]

 

 

To add or update fields for a lead, use the following snippet within your ZohoCRM service methods, specifying the field name and value according to Zoho's notation (capitalizing the first letter of each word and using underscores between words)

 

 

...
 $RecordLead->setFieldValue('Field_Name', $value);
...

 

 

This approach allows for flexible and dynamic interaction with the Zoho CRM API, enabling the creation and update of lead records with any of the available data fields.

 

 

Uploading Attachments

 

To upload an attachment, such as an image, you must first have a lead or deal created in Zoho CRM; this cannot be accomplished in a single API call. We'll adjust the contact form to accommodate file uploads.

 

 

Symfony Implementation for the Contact Form

 

 

// src/Form/ContactFormType.php
...
->add('attachment', FileType::class, ['required' => false])
...

 

 

Modify your controller to handle the file upload. If you're using Symfony version 5 or higher, inject KernelInterface to access the getProjectRoot() method. For older versions, retrieve the project directory using $this->get('kernel')->getProjectDir().

 

 

// src/Controller/ContactController.php

public function index(Request $request, ZohoCRM $zohoCRM, KernelInterface $kernelInterface) {
...
if($ContactForm->isSubmitted() &amp;amp;amp; $ContactForm->isValid()) {
...
  if($data['attachment']) {
       $directory =  $kernelInterface->getProjectDir() . '/tmp/files';
                $fileName = pathinfo($data['attachment']->getClientOriginalName(), PATHINFO_FILENAME) .'.' . $attachment->guessExtension();
                $attachment->move($directory, $fileName );
                $attachment_path = $directory . '/' . $fileName;
            }
...
    $zohoCRM->createLead(
               ...
                $attachment_path ?? null
            );

 

 

This modification saves the uploaded file in a temporary folder and passes its path to the Zoho service.

 

 

Adjusting the ZohoCRM Service for Attachments

 

Now, let's ensure the ZohoCRM service can handle the attachment:

 

 

// src/Service/ZohoCRM.php
...

public function createLead($firstName, $lastName, $email = null, $description = null, $company = null, $phone = null, $attachment = null) {
...

  try {
            $response = $Leads->createRecords($arrayOfLeads);
            $leadId = $response->getData()[0]->getEntityId();

            if ($attachment) {
                $RecordLead = ZCRMRecord::getInstance('Leads', $leadId);
                $RecordLead->uploadAttachment($attachment);
            }

        }
...

 

 

This update adds support for uploading an attachment after the lead has been created.

 

 

Testing Complete Basic Lead Integration Using the Contact Form

 

With these changes, your Symfony application can now handle file attachments in forms and upload these files to Zoho CRM as attachments to leads.

 

 

Zoho CRM API PHP lead form

 

 

Creating a Form and Controller for Deal Generation

 

Following a similar structure, you can create forms and controllers for other purposes, such as deal generation. This approach demonstrates the flexibility and power of integrating Symfony applications with external APIs like Zoho CRM for comprehensive CRM solutions.

 

 

Symfony Implementation for Deal Generation

 

Here's how you can set up a basic form and controller for generating deals in Symfony, similar to the lead generation process described above.

 

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;

class OrderFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('course', ChoiceType::class, [
                'choices' => [
                    'Symfony' => 'Symfony',
                    'Laravel' => 'Laravel',
                    'Phalcon' => 'Phalcon',
                ]
            ])
        ;
    }

}

 

 

And the corresponding controller:

 

 

<?php

namespace App\Controller;

use App\Form\OrderFormType;
use App\Service\ZohoCRM;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class OrderController extends AbstractController
{
    /**
     * @Route("/order", name="order")
     * @param Request $request
     * @param ZohoCRM $zohoCRM
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function index(Request $request, ZohoCRM $zohoCRM)
    {

        $OrderForm = $this->createForm(OrderFormType::class);

        $OrderForm->handleRequest($request);

        if ($OrderForm->isSubmitted() &amp;amp;amp;&amp;amp;amp; $OrderForm->isValid())
        {
            $data = $OrderForm->getData();
            $dealName = $data['course'];
            $zohoCRM->createDeal($dealName);

        }

        return $this->render('order/index.html.twig', [
            'Form' => $OrderForm->createView()
        ]);
    }
}

 

 

This setup allows for the generation of deals based on the selected course, demonstrating the versatility of Symfony forms for various business processes.

 

 

// templates/order/index.html.twig

{% extends 'base.html.twig' %}

{% block body %}
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-sm-10 col-lg-4 text-center text-white">
                <h1 class="mt-5">Order</h1>
                {{ form_start(Form) }}

                <div class="form-group">
                    {{ form_label(Form.course) }}
                    {{ form_widget(Form.course, {'attr': {'class': 'form-control'}}) }}
                </div>

                <div class="form-group">
                    {{ form_widget(Form.save, {'attr': {'class': 'btn btn-primary'}, 'label': 'Send message'}) }}
                </div>

                {{ form_end(Form) }}
            </div>
        </div>
    </div>
{% endblock %}

 

 

Creating a Basic Deal in Zoho CRM

 

There is only one mandatory field. You have to pass a deal name to successfully create a new deal. You can obtain the rest of the possible fields similarly to the way presented for lead generation. There are also two fields which have to be passed differently and I will cover them later. Those fields are company name and account.

 

ZohoCRM Service

 

// src/Service/ZohoCRM.php

   public function createDeal($dealName) {

        $RecordDeal = ZCRMRecord::getInstance("Deals",null);
        $RecordDeal->setFieldValue('Deal_Name', $dealName);

        $Deals = ZCRMModule::getInstance('Deals');

        $arrayOfDeals = [];
        array_push($arrayOfDeals, $RecordDeal);

        try {
            $response = $Deals->createRecords($arrayOfDeals);
            $dealId = $response->getData()[0]->getEntityId();

        }
        catch (Exception $exception) {
        // Proper exception handling recommended for production environments
            return $exception;
        }
    }

 

 

Creating a Contact and an Account

 

Before creating a lead, you must first create a contact or account and pass their respective IDs to the deal. Since an account can have many contacts, it's advisable to create the account first and then pass its ID to the contact. These IDs can subsequently be passed to the lead. You can consider an account as a company and contacts as its employees.

 

 

Adjusting the Order Form in Symfony

 

Modify the OrderFormType to include fields for first name, last name, company, and email:

 

 

// src/Form/OrderFormType.php
...
class OrderFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       ...
            ->add('firstName', TextType::class)
            ->add('lastName', TextType::class)
            ->add('company', TextType::class)
            ->add('email', EmailType::class ,['required' => false])
       ...
    }

}

 

 

Next, adjust the OrderController to handle these new fields:

 

 

// src/Controller/OrderController.php
...
            $data = $OrderForm->getData();
            $dealName = $data['course'];
            $firstName = $data['firstName'];
            $lastName = $data['lastName'];
            $email = $data['email'];
            $account = $data['company'];



            $zohoCRM->createDeal(
                $dealName,
                $firstName,
                $lastName,
                $email,
                $account

            );
...

 

 

Extending the ZohoCRM Service

 

To enhance the functionality of our ZohoCRM service, we will introduce four new methods. These methods are designed to streamline the creation and association of accounts and contacts with deals. Initially, focus on making these methods functional; optimization and refactoring can be addressed subsequently.

 

Service Implementation

 

Located at src/Service/ZohoCRM.php, add the following methods:

 

Create Account

 

 

public function createAccount($companyName) {
    if (!empty($companyName)) {
        $recordAccount = ZCRMRecord::getInstance("Accounts", null);
        $recordAccount->setFieldValue('Account_Name', $companyName);
        $array = [$recordAccount];

        $accountsModule = ZCRMModule::getInstance('Accounts');
        $response = $accountsModule->createRecords($array);

        return $response->getData()[0]->getEntityId();
    }

    return null;
}

 

 

Find Contact ID by Email

 

 

public function findContactId($email) {
    $contactsModule = ZCRMModule::getInstance('Contacts');

    try {
        $result = $contactsModule->searchRecordsByEmail($email);
        if (!empty($result->getData())) {
            return $result->getData()[0]->getEntityId();
        }
    } catch (\Exception $exception) {
        return null;
    }
}

 

 

Create Contact

 

 

public function createContact($firstName, $lastName, $email, $accountId = null) {
    $recordContact = ZCRMRecord::getInstance("Contacts", null);
    $recordContact->setFieldValue('First_Name', $firstName);
    $recordContact->setFieldValue('Last_Name', $lastName);
    $recordContact->setFieldValue('Email', $email);

    if ($accountId) {
        $recordContact->setFieldValue('Account_Id', $accountId);
    }

    $array = [$recordContact];
    $contactsModule = ZCRMModule::getInstance('Contacts');
    $response = $contactsModule->createRecords($array);

    return $response->getData()[0]->getEntityId();
}

 

 

Enhancing the CreateDeal Method

 

Adjust the createDeal method to include the logic for account and contact creation/association:

 

 

public function createDeal($dealName, $firstName, $lastName, $email, $company = null) {
    $accountId = $company ? $this->createAccount($company) : null;
    $contactId = $this->createContact($firstName, $lastName, $email, $accountId);

    $recordDeal = ZCRMRecord::getInstance("Deals", null);
    $recordDeal->setFieldValue('Deal_Name', $dealName);
    if ($contactId) $recordDeal->setFieldValue('Contact_Id', $contactId);
    if ($accountId) $recordDeal->setFieldValue('Account_Id', $accountId);

    $dealsModule = ZCRMModule::getInstance('Deals');
    $arrayOfDeals = [$recordDeal];

    try {
        $response = $dealsModule->createRecords($arrayOfDeals);
        return $response->getData()[0]->getEntityId();
    } catch (Exception $exception) {
        // Proper exception handling recommended for production environments
        return $exception;
    }
}

 

 

Testing the Integration

 

 

With these enhancements, your ZohoCRM service is now equipped to handle the creation of deals with associated accounts and contacts. Test the integration by submitting data through the order form, ensuring that the deal, along with its related account and contact, are successfully created in Zoho CRM.

This approach ensures that the service is not only functional but also ready for future optimization, providing a solid foundation for integrating Symfony applications with Zoho CRM's capabilities.

 

Zoho CRM API PHP deal form