blowstack logo
  • BlowStack
  • ->
  • Blog
  • ->
  • How to use Zoho CRM API with PHP - Create Leads and Deals

How to use Zoho CRM API with PHP

Create Leads and Deals

Last update

20 min.
  1. Prepare a form and a controller for leads generation.
  2. Create a basic lead.
  3. Get all possible lead fields.
  4. Uploading attachments.
  5. Prepare a form and a controller for deals generation.
  6. Create a basic deal.
  7. Create a contact and an account.

 

 

Before jumping into manual, you can clone ready boilerplate Zoho CRM implemention for:

Laravel

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

or

Symfony

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

 

 

 

To follow this article

You should have already implemented a service which handles the authentication process. I cover how to do it in this post (at the end you will find code snipets). Every Zoho API call have to pass a config and an access token so using this service will make things easier for you and the many of following solutions will be implemented in this service (ZohoCRM).

 

LEADS

 

Prepare a form and a controller for leads generation

 

For the purpose of lead generation I created a contact form which collects basic data. After that you will encounter a controller and a view. All these have their own separate implementations in Symfony and Laravel and this approach is common for the whole post. The same code for these frameworks always lays in 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)
        ;
    }
}

 

Then create a controller to handle the form and the view for it.

<?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(),
        ]);
    }
}

 

 

// 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 8 basic and the most popular fields which a lead can consist of. Two of them are obligatory (first and last name). Hereunder you will find the simplest implementation which covers 8 basic fields that can be passed to the CRM. In order to handle that let's add a new method in the service.

 

  • ZohoCRM

 

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

 

So what about the rest of the fields?  There are an easy way to find out all the fields that can be passed. Just query the CRM after you inserted you first lead. As this can be usefull in the future let's add a function for searching a record from any module not only a lead.

 

  • ZohoCRM

 

// src/Service/ZohoCRM
...

function getRecords($moduleName, $email) {

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

 

I chose to search by email but there are more options available like:

  • searchRecordsByPhone
  • searchRecordsByWord
  • searchRecordsByCriteria

 

The last one takes array and gives you the most flexible way of searching. As you can search by any field (i.e. last name ['Last_Name' => $lastName]) or their combination.

To make a quick look at the response create a new ZohoController and Twig template. Then pass the lead email address to the url.

 

Symfony implementation


 

<?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,
        ]);
    }
}

 

 

// templates/admin/lead.html.twig

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

{% block title %}Hello ZohoController!{% endblock %}

{% block body %}
    {{ dump(lead) }}
{% endblock %}

 

The function returns the structure where you should find data reponse in Json format which represents all available fields that can be sent to the Zoho CRM. Notice that the method returns multiple records from Leads object if conditions of search are met.

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 [▶]
  ]

 

As you see there are plenty of possible data to pass. To pass any of them just add the below code with desired field name and value. Please notice a strange Zoho field notation (capital first letters of every word and underscore between).

 

  • ZohoCRM

 

// src/Service/ZohoCRM.php
...
 $RecordLead->setFieldValue('Field_Name', $value);
...

 

 

Uploading attachments

 

It's worth mentioning that if you wan't to upload an image, you shoudl first have already created a lead or deal and it can't be done during one API call.

Let's adjust a little bit the contact form to make it possible.

 

Symfony implementation


 

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

 

You have to change the controller as well a bit. Inject KernelInterface to have access to the getProjectRoot() method if you use Symfony ≥ 5. If not get this value from the kernel service ($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
            );

 

This way your controller saved the file in the temporary folder and passes it's url to the Zoho service.

It's time to adjust the Zoho service so it will handle the attachments properly.

 

  • ZohoCRM

 

// 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);
            }

        }
...

 

 

Test complete basic lead integration using the contact form

 

Zoho CRM API PHP lead form

 

 

DEALS

 

Prepare a form and a controller for deals generation

 

 

Symfony implementation


 

<?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',
                ]
            ])
        ;
    }

}

 

<?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()
        ]);
    }
}

 

 

// 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 %}

 

 

Create a basic deal

 

  • ZohoCRM

 

There is only one mandatory field. You have to pass deal name to successfully create a new deal. You can obtain the rest possible fields simillary 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.

// 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) {
            // this is not proper way to handle exceptions on the production env
            return $exception;
        }
    }

 

 

Create a contact and an account

 

The contact and the account fields have to be created before a lead. It means that you have first create a contact or account and pass the respective id to a deal. An account can have many contacts so if you want to create both, the account one should be created first and it's id has to be passed to the contact. Then the both ids can be passed further to a lead. You can interpret an account field as a company and contacts as it's employees.

Let's change the form to make it possible to pass a new data to the Controller.

 

Symfony implementation


 

// 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])
       ...
    }

}

 

// 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

            );
...

 

  • ZohoCRM

 

Add 4 new methods to the service. Two methods per field. You can factorize this code later but for now it's important to make it working.

// src/Service/ZohoCRM
...
	public function createAccount($company_name) {

        if ($company_name != null) {

            $RecordAccount = ZCRMRecord::getInstance("Accounts", null);
            $RecordAccount->setFieldValue('Account_Name', $company_name);
            $array[0] = $RecordAccount;

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

            return $Response->getData()[0]->getEntityId();
        }
        else {
            return null;
        }
    }

    public function findContactId($email) {

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

        try {
            $Contact = $Contacts->searchRecordsByEmail($email);
            return $Contact->getData()[0]->getEntityId();
        }
        catch (\Exception $exception) {
            return null;
        }
    }

    public function createContact($first_name, $last_name, $email, $account_id = null) {

        $Contacts = ZCRMModule::getInstance('Contacts');
        $RecordContact = ZCRMRecord::getInstance("Contacts",null);
        $RecordContact->setFieldValue('First_Name', $first_name);
        $RecordContact->setFieldValue('Last_Name', $last_name);
        $RecordContact->setFieldValue('Email',$email);
        if ($account_id) {
            $RecordContact->setFieldValue('Account_Name',$account_id);
        }

        $array[0] = $RecordContact;
        $Response = $Contacts->createRecords($array);

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

 

Next change the createDeal method to incorporate new fields.

  public function createDeal($dealName, $firstName, $lastName, $email, $account = null) {

        $RecordDeal = ZCRMRecord::getInstance("Deals",null);

        $accountId = null;
        $contactId = null;

        if ($account) {
            $accountId = $this->findAccountId($account) ?? $this->createAccount($account);
        }

        if ($firstName &amp;amp;amp;&amp;amp;amp; $lastName &amp;amp;amp;&amp;amp;amp; $email) {
            $contactId = $this->findContactId($email) ?? $this->createContact($firstName, 		$lastName, $email, $accountId) ;
        }


        $RecordDeal->setFieldValue('Deal_Name', $dealName);
        $RecordDeal->setFieldValue('Contact_Name', $contactId);
        $RecordDeal->setFieldValue('Account_Name', $accountId);

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

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

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

        }
        catch (Exception $exception) {
            // this is not proper way to handle exceptions on the production env
            return $exception;
        }
    }

 

 

Test the integration using the order form

 

Zoho CRM API PHP deal form

 

 

Recent posts

BlowStack 2021
Portfolio Cheat
sheets