For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at http://ascentbusiness.co.uk/zoho-support-2.  For larger projects, check our bespoke pricing structure and receive dedicated support from our hands-on project consultants and developers at http://ascentbusiness.co.uk/crm-solutions/zoho-crm-packages-prices.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at http://ascentbusiness.co.uk.

ZohoCRM & Xero Real-Time Invoices: Receive Webhook

What?
So this is an article expanding on my article Zoho Deluge - Connect to Xero API and explores how instead of a schedule, we can get Xero to tell ZohoCRM whenever an invoice or contact gets updated in Xero.

Why?
I used to use ZohoCRM schedules to pull when an invoice has been paid, but ZohoCRM schedules run only every 2 hours. You can set up 2 schedules: one that runs on even hours and one that runs on odd hours to change this to every hour. It's not enough for some, so the below details on how you can get Xero to tell Zoho when a contact or invoice has been updated immediately.

How?
Here's an overview of how we achieve this:
  1. Set up a Zoho CRM function to receive the webhook.
  2. Setup a webhook in Xero.
  3. [Optional for Method #2] Setup a PHP script on a server that will validate the Xero webhook.
You're going to need to respond to Xero with valid and invalid HTTP statuses as their webhook validation process is a little stricter than Zoho's.

Setup a Zoho CRM Function:
  1. Login to Zoho CRM > Setup > Functions > New Function
  2. Function Name: fn_API_ReceiveXeroWebhook
  3. Display Name: FN - Xero - Receive Webhook
  4. Description: Function used to receive webhooks from Xero and parse these into data for invoices and contacts.
  5. Category: Standalone
  6. Click on Create
  7. Give it the parameter: crmAPIRequest (string)
  8. Dump in the following code:
    copyraw
    // enter the webhooks key for your app (in Xero under Manage App > Webhooks)
    v_Webhook_Key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI+111122223333444455556666777788889/a1b2c3d4e5f6g7h8i9==";
    //
    // receive parameter crmAPIRequest (of datatype string in your function)
    m_Payload = crmAPIRequest;
    //
    // initialize response variable
    m_Response = Map();
    //
    // get xero signature
    v_XeroSignature = "SIGNATURE_FAIL";
    if(!isnull(m_Payload.get("headers")))
    {
    	if(!isnull(m_Payload.get("headers").get("x-xero-signature")))
    	{
    		v_XeroSignature = m_Payload.get("headers").get("x-xero-signature");
    	}
    }
    //
    // encrypt body with Sha-256
    v_WebhookBodyHMACSHA256 = "ENCRYPTION_FAIL";
    v_ResponseCode = 401;
    if(!isnull(m_Payload.get("body")))
    {
    	v_WebhookBodyHMACSHA256 = zoho.encryption.hmacsha256(v_Webhook_Key,m_Payload.get("body"));
    }
    if(v_WebhookBodyHMACSHA256 == v_XeroSignature)
    {
    	v_ResponseCode = 200;
    }
    m_Response.put("status_code",v_ResponseCode);
    //
    // return
    return {"crmAPIResponse":m_Response};
    1.  // enter the webhooks key for your app (in Xero under Manage App > Webhooks) 
    2.  v_Webhook_Key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI+111122223333444455556666777788889/a1b2c3d4e5f6g7h8i9=="
    3.  // 
    4.  // receive parameter crmAPIRequest (of datatype string in your function) 
    5.  m_Payload = crmAPIRequest; 
    6.  // 
    7.  // initialize response variable 
    8.  m_Response = Map()
    9.  // 
    10.  // get xero signature 
    11.  v_XeroSignature = "SIGNATURE_FAIL"
    12.  if(!isnull(m_Payload.get("headers"))) 
    13.  { 
    14.      if(!isnull(m_Payload.get("headers").get("x-xero-signature"))) 
    15.      { 
    16.          v_XeroSignature = m_Payload.get("headers").get("x-xero-signature")
    17.      } 
    18.  } 
    19.  // 
    20.  // encrypt body with Sha-256 
    21.  v_WebhookBodyHMACSHA256 = "ENCRYPTION_FAIL"
    22.  v_ResponseCode = 401
    23.  if(!isnull(m_Payload.get("body"))) 
    24.  { 
    25.      v_WebhookBodyHMACSHA256 = zoho.encryption.hmacsha256(v_Webhook_Key,m_Payload.get("body"))
    26.  } 
    27.  if(v_WebhookBodyHMACSHA256 == v_XeroSignature) 
    28.  { 
    29.      v_ResponseCode = 200
    30.  } 
    31.  m_Response.put("status_code",v_ResponseCode)
    32.  // 
    33.  // return 
    34.  return {"crmAPIResponse":m_Response}
  9. Save this and return to the list of functions
  10. Hover over the function you just created, click on the 3 dots/ellipsis and select "REST API"
  11. Switch on the API Key and copy the URL to clipboard or a text editor
  12. Click on "Save"
Setup a Xero webhook:
  1. Login to the page https://developer.xero.com and click on "My Apps".
  2. Select the app you are going to setup a webhook for (see my Zoho Deluge - Connect to Xero API article for setting up an app)
  3. Select "Webhooks" in the left sidebar menu
  4. Tick "Contacts" and "Invoices" and enter the Delivery URL as the REST API Function you noted above:
    ZohoCRM & Xero Real-Time Invoices: Receive Webhook : Setup in Xero
  5. Copy the Webhooks key on the Xero page into your CRM function replacing the value in Line 2 of my code snippet above. > Save the CRM function.
  6. Click on "Save" on the Xero page
  7. Click on "Send 'Intent to receive'"
  8. You should get an OK status:
    ZohoCRM & Xero Real-Time Invoices: Status OK
  9. Go back into your CRM function and edit the function to do what you need it to (eg. update a contact or invoice), I'm putting what I usually need to do here as a template:
    copyraw
    //
    // *******************************************************************************
    // Validate Xero webhook
    // 
    // enter the webhooks key for your app (in Xero under Manage App > Webhooks)
    v_Webhook_Key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI+111122223333444455556666777788889/a1b2c3d4e5f6g7h8i9==";
    //
    // receive parameter crmAPIRequest (of datatype string in your function)
    m_Payload = crmAPIRequest;
    //
    // initialize response variable
    m_Response = Map();
    //
    // get xero signature
    v_XeroSignature = "SIGNATURE_FAIL";
    if(!isnull(m_Payload.get("headers")))
    {
    	if(!isnull(m_Payload.get("headers").get("x-xero-signature")))
    	{
    		v_XeroSignature = m_Payload.get("headers").get("x-xero-signature");
    	}
    }
    //
    // encrypt body to check against the signature
    v_WebhookBodyHMACSHA256 = "ENCRYPTION_FAIL";
    v_ResponseCode = 401;
    if(!isnull(m_Payload.get("body")))
    {
    	v_WebhookBodyHMACSHA256 = zoho.encryption.hmacsha256(v_Webhook_Key,m_Payload.get("body"));
    }
    if(v_WebhookBodyHMACSHA256 == v_XeroSignature)
    {
    	v_ResponseCode = 200;
    }
    m_Response.put("status_code",v_ResponseCode);
    //
    // *******************************************************************************
    // Generate your access token to connect to Xero and query Xero records
    // See my article: https://www.joellipman.com/articles/crm/zoho/zoho-deluge-sync-to-xero-api.html
    //
    v_TenantID = "";
    v_AccessToken = "";
    r_Response = getUrl("https://www.zohoapis.com/crm/v2/functions/fn_api_getxeroaccesstoken/actions/execute?auth_type=apikey&zapikey=<your_zapi_key>");
    if(!isnull(r_Response.toMap().get("details")))
    {
    	if(!isnull(r_Response.toMap().get("details").get("output")))
    	{
    		v_AccessToken = r_Response.toMap().get("details").get("output");
    	}
    }
    m_Header = Map();
    m_Header.put("Authorization","Bearer " + v_AccessToken);
    m_Header.put("Accept","application/json");
    m_Header.put("Xero-tenant-id",v_TenantID);
    //
    // *******************************************************************************
    // Parse body of Xero webhook and update respective CRM record
    //
    m_Body = ifnull(m_Payload.get("body"),m_Blank);
    //
    // check if events was in the payload
    if(!isnull(m_Body.get("events")))
    {
    	//
    	// parse events out of the webhook (a list)
    	l_Events = m_Body.get("events").toJSONList();
    	//
    	// loop through the events
    	for each  r_Event in l_Events
    	{
    		if(!isnull(r_Event.get("eventCategory")))
    		{
    			//
    			// event type will be UPDATE and eventCategory will be our module
    			v_Event = r_Event.get("eventCategory") + " " + r_Event.get("eventType");
    			//
    			// get the xero ID of the record (hexadecimal)
    			v_Xero_ResourceID = r_Event.get("resourceId");
    			//
    			// do different things based on the category of the update
    			if(r_Event.get("eventCategory").containsIgnoreCase("CONTACT"))
    			{
    				// get contact record from Xero to see what changed...
    				r_XeroContact = invokeurl
    				[
    					url :v_DataEndpoint + "/Contacts/" + v_Xero_ResourceID
    					type :GET
    					headers:m_Header
    				];
    				// 
    				if(!isnull(r_XeroContact.get("Contacts")))
    				{
    					for each  r_ThisContact in r_XeroContact.get("Contacts")
    					{
    						//
    						// checks if in system already (update or create)
    						if(!isnull(r_ThisContact.get("ContactID")))
    						{
    							// check if already Xero Ref ID exists in CRM Contacts
    							v_CrmContactID = 0;
    							l_SearchResultsByRef = zoho.crm.searchRecords("Contacts","Xero_Ref_ID:equals:" + v_Xero_ResourceID);
    							for each  r_Result1 in l_SearchResultsByRef
    							{
    								if(!isnull(r_Result1.get("id")))
    								{
    									v_CrmContactID = r_Result1.get("id").toLong();
    									v_ZohoModule = "Contacts";
    								}
    							}
    							// 
    							if(v_CrmContactID != 0)
    							{
    								// parse and build up CRM record update here
    							}
    							else
    							{
    								// parse and build up CRM record creation here
    							}
    						}
    					}
    				}
    			}
    			else if(r_Event.get("eventCategory").containsIgnoreCase("INVOICE"))
    			{
    				// get invoice record from Xero to see what changed...
    				r_XeroInvoice = invokeurl
    				[
    					url :v_DataEndpoint + "/Invoices/" + v_Xero_ResourceID
    					type :GET
    					headers:m_Header
    				];
    				// 
    				if(!isnull(r_XeroInvoice.get("Invoices")))
    				{
    					for each  r_ThisInvoice in r_XeroInvoice.get("Invoices")
    					{
    						if(!isnull(r_ThisInvoice.get("InvoiceID")))
    						{
    							//
    							// find the invoice in CRM
    							v_CrmInvoiceID = 0;
    							l_SearchCrmByXeroID = zoho.crm.searchRecords("Invoices","Xero_Ref_ID:equals:" + v_Xero_ResourceID);
    							for each  r_Result1 in l_SearchCrmByXeroID
    							{
    								if(!isnull(r_Result1.get("id")))
    								{
    									v_CrmInvoiceID = r_Result1.get("id").toLong();
    									v_ZohoModule = "Invoices";
    								}
    							}
    							// 
    							if(v_CrmInvoiceID != 0)
    							{
    								// parse and build up CRM record update here
    							}
    							else
    							{
    								// parse and build up CRM record creation here
    							}
    
    						}
    					}
    				}
    			}
    		}
    	}
    }
    // 
    // return 
    return {"crmAPIResponse":m_Response};
    1.  // 
    2.  // ******************************************************************************* 
    3.  // Validate Xero webhook 
    4.  // 
    5.  // enter the webhooks key for your app (in Xero under Manage App > Webhooks) 
    6.  v_Webhook_Key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI+111122223333444455556666777788889/a1b2c3d4e5f6g7h8i9=="
    7.  // 
    8.  // receive parameter crmAPIRequest (of datatype string in your function) 
    9.  m_Payload = crmAPIRequest; 
    10.  // 
    11.  // initialize response variable 
    12.  m_Response = Map()
    13.  // 
    14.  // get xero signature 
    15.  v_XeroSignature = "SIGNATURE_FAIL"
    16.  if(!isnull(m_Payload.get("headers"))) 
    17.  { 
    18.      if(!isnull(m_Payload.get("headers").get("x-xero-signature"))) 
    19.      { 
    20.          v_XeroSignature = m_Payload.get("headers").get("x-xero-signature")
    21.      } 
    22.  } 
    23.  // 
    24.  // encrypt body to check against the signature 
    25.  v_WebhookBodyHMACSHA256 = "ENCRYPTION_FAIL"
    26.  v_ResponseCode = 401
    27.  if(!isnull(m_Payload.get("body"))) 
    28.  { 
    29.      v_WebhookBodyHMACSHA256 = zoho.encryption.hmacsha256(v_Webhook_Key,m_Payload.get("body"))
    30.  } 
    31.  if(v_WebhookBodyHMACSHA256 == v_XeroSignature) 
    32.  { 
    33.      v_ResponseCode = 200
    34.  } 
    35.  m_Response.put("status_code",v_ResponseCode)
    36.  // 
    37.  // ******************************************************************************* 
    38.  // Generate your access token to connect to Xero and query Xero records 
    39.  // See my article: https://www.joellipman.com/articles/crm/zoho/zoho-deluge-sync-to-xero-api.html 
    40.  // 
    41.  v_TenantID = ""
    42.  v_AccessToken = ""
    43.  r_Response = getUrl("https://www.zohoapis.com/crm/v2/functions/fn_api_getxeroaccesstoken/actions/execute?auth_type=apikey&zapikey=<your_zapi_key>")
    44.  if(!isnull(r_Response.toMap().get("details"))) 
    45.  { 
    46.      if(!isnull(r_Response.toMap().get("details").get("output"))) 
    47.      { 
    48.          v_AccessToken = r_Response.toMap().get("details").get("output")
    49.      } 
    50.  } 
    51.  m_Header = Map()
    52.  m_Header.put("Authorization","Bearer " + v_AccessToken)
    53.  m_Header.put("Accept","application/json")
    54.  m_Header.put("Xero-tenant-id",v_TenantID)
    55.  // 
    56.  // ******************************************************************************* 
    57.  // Parse body of Xero webhook and update respective CRM record 
    58.  // 
    59.  m_Body = ifnull(m_Payload.get("body"),m_Blank)
    60.  // 
    61.  // check if events was in the payload 
    62.  if(!isnull(m_Body.get("events"))) 
    63.  { 
    64.      // 
    65.      // parse events out of the webhook (a list) 
    66.      l_Events = m_Body.get("events").toJSONList()
    67.      // 
    68.      // loop through the events 
    69.      for each  r_Event in l_Events 
    70.      { 
    71.          if(!isnull(r_Event.get("eventCategory"))) 
    72.          { 
    73.              // 
    74.              // event type will be UPDATE and eventCategory will be our module 
    75.              v_Event = r_Event.get("eventCategory") + " " + r_Event.get("eventType")
    76.              // 
    77.              // get the xero ID of the record (hexadecimal) 
    78.              v_Xero_ResourceID = r_Event.get("resourceId")
    79.              // 
    80.              // do different things based on the category of the update 
    81.              if(r_Event.get("eventCategory").containsIgnoreCase("CONTACT")) 
    82.              { 
    83.                  // get contact record from Xero to see what changed... 
    84.                  r_XeroContact = invokeUrl 
    85.                  [ 
    86.                      url :v_DataEndpoint + "/Contacts/" + v_Xero_ResourceID 
    87.                      type :GET 
    88.                      headers:m_Header 
    89.                  ]
    90.                  // 
    91.                  if(!isnull(r_XeroContact.get("Contacts"))) 
    92.                  { 
    93.                      for each  r_ThisContact in r_XeroContact.get("Contacts") 
    94.                      { 
    95.                          // 
    96.                          // checks if in system already (update or create) 
    97.                          if(!isnull(r_ThisContact.get("ContactID"))) 
    98.                          { 
    99.                              // check if already Xero Ref ID exists in CRM Contacts 
    100.                              v_CrmContactID = 0
    101.                              l_SearchResultsByRef = zoho.crm.searchRecords("Contacts","Xero_Ref_ID:equals:" + v_Xero_ResourceID)
    102.                              for each  r_Result1 in l_SearchResultsByRef 
    103.                              { 
    104.                                  if(!isnull(r_Result1.get("id"))) 
    105.                                  { 
    106.                                      v_CrmContactID = r_Result1.get("id").toLong()
    107.                                      v_ZohoModule = "Contacts"
    108.                                  } 
    109.                              } 
    110.                              // 
    111.                              if(v_CrmContactID != 0) 
    112.                              { 
    113.                                  // parse and build up CRM record update here 
    114.                              } 
    115.                              else 
    116.                              { 
    117.                                  // parse and build up CRM record creation here 
    118.                              } 
    119.                          } 
    120.                      } 
    121.                  } 
    122.              } 
    123.              else if(r_Event.get("eventCategory").containsIgnoreCase("INVOICE")) 
    124.              { 
    125.                  // get invoice record from Xero to see what changed... 
    126.                  r_XeroInvoice = invokeUrl 
    127.                  [ 
    128.                      url :v_DataEndpoint + "/Invoices/" + v_Xero_ResourceID 
    129.                      type :GET 
    130.                      headers:m_Header 
    131.                  ]
    132.                  // 
    133.                  if(!isnull(r_XeroInvoice.get("Invoices"))) 
    134.                  { 
    135.                      for each  r_ThisInvoice in r_XeroInvoice.get("Invoices") 
    136.                      { 
    137.                          if(!isnull(r_ThisInvoice.get("InvoiceID"))) 
    138.                          { 
    139.                              // 
    140.                              // find the invoice in CRM 
    141.                              v_CrmInvoiceID = 0
    142.                              l_SearchCrmByXeroID = zoho.crm.searchRecords("Invoices","Xero_Ref_ID:equals:" + v_Xero_ResourceID)
    143.                              for each  r_Result1 in l_SearchCrmByXeroID 
    144.                              { 
    145.                                  if(!isnull(r_Result1.get("id"))) 
    146.                                  { 
    147.                                      v_CrmInvoiceID = r_Result1.get("id").toLong()
    148.                                      v_ZohoModule = "Invoices"
    149.                                  } 
    150.                              } 
    151.                              // 
    152.                              if(v_CrmInvoiceID != 0) 
    153.                              { 
    154.                                  // parse and build up CRM record update here 
    155.                              } 
    156.                              else 
    157.                              { 
    158.                                  // parse and build up CRM record creation here 
    159.                              } 
    160.   
    161.                          } 
    162.                      } 
    163.                  } 
    164.              } 
    165.          } 
    166.      } 
    167.  } 
    168.  // 
    169.  // return 
    170.  return {"crmAPIResponse":m_Response}

If you don't get an OK message then check your CRM code matches as above (with different v_Webhook_Key). If it still doesn't work then try solution #2: using a third-party server detailed further in this article.

Solution #2: Using Third-Party Server
If this doesn't work, you will need to consider using a third-party server (my example here is a LAMP server):

  1. Complete webhook setup by passing 'Intent to receive' required stage.
    ZohoCRM & Xero Real-Time Invoices: Intent to Receive REQUIRED
    Xero will test both a valid and invalid signed webhook. So I'm going to cheat, by setting up a PHP script on a server we own to return a 200 status:

    1. Have a hosting server ready which has SSL installed.
    2. Add a PHP script with the following code (change the webhooks key to your own - and REST API function URL):
      copyraw
      <?php
      
      // enter the webhooks key for your app (in Xero under Manage App > Webhooks)
      $v_Xero_Webhooks_Key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI+111122223333444455556666777788889/a1b2c3d4e5f6g7h8i9==";
      
      // get the headers
      $a_Headers = getallheaders();
      $v_Xero_Signature = $a_Headers[ 'x-xero-signature' ];
      
      // get the body
      $j_Body = @file_get_contents( "php://input" );
      
      // compute a sha256 hash (HMACSHA256) using the body and your secret key
      $v_Hashed_Payload = base64_encode( hash_hmac( "sha256", $j_Body, $v_Xero_Webhooks_Key, true) );
      
      // timing attack safe string comparison
      if ( hash_equals( $v_Xero_Signature, $v_Hashed_Payload ) ){
      
          // return response to Xero
          header( "HTTP/1.1 200 OK" );
          http_response_code( 200 );
      
          // took me a few hours to work out this code and get Xero to approve of this webhook so let's now push the data to Zoho
          $v_Zoho_Url = "https://www.zohoapis.com/crm/v2/functions/fn_api_receivexerowebhook/actions/execute?auth_type=apikey&zapikey=<YOUR_ZOHO_API_KEY>";
      
          // build and send via cURL
          $ch = curl_init();
          curl_setopt( $ch, CURLOPT_HEADER, 0 );
          curl_setopt( $ch, CURLOPT_URL, $v_Zoho_Url );
          curl_setopt( $ch, CURLOPT_POST, 1 );
          curl_setopt( $ch, CURLOPT_POSTFIELDS, $j_Body );
          curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
          $r_Zoho_Response = curl_exec( $ch );
          curl_close( $ch );
      
      }else{
      
          // return response to Xero (used as Xero will test both a valid and invalid request)
          header('HTTP/1.1 401 Unauthorized');
          http_response_code(401);
      
      }
      ?>
      1.  <?php 
      2.   
      3.  // enter the webhooks key for your app (in Xero under Manage App > Webhooks) 
      4.  $v_Xero_Webhooks_Key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHI+111122223333444455556666777788889/a1b2c3d4e5f6g7h8i9=="
      5.   
      6.  // get the headers 
      7.  $a_Headers = getallheaders()
      8.  $v_Xero_Signature = $a_Headers[ 'x-xero-signature' ]
      9.   
      10.  // get the body 
      11.  $j_Body = @file_get_contents( "php://input" )
      12.   
      13.  // compute a sha256 hash (HMACSHA256) using the body and your secret key 
      14.  $v_Hashed_Payload = base64_encode( hash_hmac( "sha256", $j_Body, $v_Xero_Webhooks_Key, true) )
      15.   
      16.  // timing attack safe string comparison 
      17.  if ( hash_equals( $v_Xero_Signature, $v_Hashed_Payload ) ){ 
      18.   
      19.      // return response to Xero 
      20.      header( "HTTP/1.1 200 OK" )
      21.      http_response_code( 200 )
      22.   
      23.      // took me a few hours to work out this code and get Xero to approve of this webhook so let's now push the data to Zoho 
      24.      $v_Zoho_Url = "https://www.zohoapis.com/crm/v2/functions/fn_api_receivexerowebhook/actions/execute?auth_type=apikey&zapikey=<YOUR_ZOHO_API_KEY>"
      25.   
      26.      // build and send via cURL 
      27.      $ch = curl_init()
      28.      curl_setopt( $ch, CURLOPT_HEADER, 0 )
      29.      curl_setopt( $ch, CURLOPT_URL, $v_Zoho_Url )
      30.      curl_setopt( $ch, CURLOPT_POST, 1 )
      31.      curl_setopt( $ch, CURLOPT_POSTFIELDS, $j_Body )
      32.      curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true )
      33.      $r_Zoho_Response = curl_exec( $ch )
      34.      curl_close( $ch )
      35.   
      36.  }else{ 
      37.   
      38.      // return response to Xero (used as Xero will test both a valid and invalid request) 
      39.      header('HTTP/1.1 401 Unauthorized')
      40.      http_response_code(401)
      41.   
      42.  } 
      43.  ?> 
    3. Change the Delivery URL to the location of this PHP script and click on "Save"
    4. Click on "Send 'intent to receive' and Xero will test both a valid and invalid signed webhook.
    5. You should get an OK status:
      ZohoCRM & Xero Real-Time Invoices: Status OK
  2. Now return to your ZohoCRM function and here's the code I'm using for this:
    copyraw
    //
    // init
    m_Blank = Map();
    m_Payload = crmAPIRequest;
    m_Body = ifnull(m_Payload.get("body"),m_Blank);
    //
    // check if events was in the payload
    if(!isnull(m_Body.get("events")))
    {
    	//
    	// parse events out of the webhook (a list)
    	l_Events = m_Body.get("events").toJSONList();
    	//
    	// loop through the events
    	for each r_Event in l_Events
        {
    		if(!isnull(r_Event.get("eventCategory")))
    		{
    			//
    			// event type will be UPDATE and eventCategory will be our module
    			v_Event = r_Event.get("eventCategory") + " " + r_Event.get("eventType");
    			//
    			// get the xero ID of the record (hexadecimal)
    			v_Xero_ResourceID = r_Event.get("resourceId");
    			//
    			// do different things based on the category of the update
    			if(r_Event.get("eventCategory").containsIgnoreCase("CONTACT"))
    			{
    				// get contact record from Xero to see what changed...
    			}
    			else if(r_Event.get("eventCategory").containsIgnoreCase("INVOICE"))
    			{
    				// get invoice record from Xero to see what changed...
    			}
    		}
        }
    }
    return "";
    1.  // 
    2.  // init 
    3.  m_Blank = Map()
    4.  m_Payload = crmAPIRequest; 
    5.  m_Body = ifnull(m_Payload.get("body"),m_Blank)
    6.  // 
    7.  // check if events was in the payload 
    8.  if(!isnull(m_Body.get("events"))) 
    9.  { 
    10.      // 
    11.      // parse events out of the webhook (a list) 
    12.      l_Events = m_Body.get("events").toJSONList()
    13.      // 
    14.      // loop through the events 
    15.      for each r_Event in l_Events 
    16.      { 
    17.          if(!isnull(r_Event.get("eventCategory"))) 
    18.          { 
    19.              // 
    20.              // event type will be UPDATE and eventCategory will be our module 
    21.              v_Event = r_Event.get("eventCategory") + " " + r_Event.get("eventType")
    22.              // 
    23.              // get the xero ID of the record (hexadecimal) 
    24.              v_Xero_ResourceID = r_Event.get("resourceId")
    25.              // 
    26.              // do different things based on the category of the update 
    27.              if(r_Event.get("eventCategory").containsIgnoreCase("CONTACT")) 
    28.              { 
    29.                  // get contact record from Xero to see what changed... 
    30.              } 
    31.              else if(r_Event.get("eventCategory").containsIgnoreCase("INVOICE")) 
    32.              { 
    33.                  // get invoice record from Xero to see what changed... 
    34.              } 
    35.          } 
    36.      } 
    37.  } 
    38.  return ""
  3. You can test this by updating a contact or draft invoice record.

Additional Note(s):
  • You should have a field (single-line) called Xero Ref ID on both the contact/invoice module for referring to so as to push/pull deltas.
  • After all that work, you only get the ID of a record that was changed. Here's a sample response:
    copyraw
    {
      "events": [
        {
          "resourceUrl": "https://api.xero.com/api.xro/2.0/Contacts/aaaabbbb-1111-cccc-2222-d3e4f5d3e4f5",
          "resourceId": "aaaabbbb-1111-cccc-2222-d3e4f5d3e4f5",
          "eventDateUtc": "2022-01-10T17:53:48.886",
          "eventType": "UPDATE",
          "eventCategory": "CONTACT",
          "tenantId": "99998888-ffff-7777-eeee-d6e5f4d6e5f4",
          "tenantType": "ORGANISATION"
        }
      ],
      "firstEventSequence": 5,
      "lastEventSequence": 5,
      "entropy": "ABCDEFGHIJKLMNOPQRST"
    }
    1.  { 
    2.    "events": [ 
    3.      { 
    4.        "resourceUrl": "https://api.xero.com/api.xro/2.0/Contacts/aaaabbbb-1111-cccc-2222-d3e4f5d3e4f5", 
    5.        "resourceId": "aaaabbbb-1111-cccc-2222-d3e4f5d3e4f5", 
    6.        "eventDateUtc": "2022-01-10T17:53:48.886", 
    7.        "eventType": "UPDATE", 
    8.        "eventCategory": "CONTACT", 
    9.        "tenantId": "99998888-ffff-7777-eeee-d6e5f4d6e5f4", 
    10.        "tenantType": "ORGANISATION" 
    11.      } 
    12.    ], 
    13.    "firstEventSequence": 5, 
    14.    "lastEventSequence": 5, 
    15.    "entropy": "ABCDEFGHIJKLMNOPQRST" 
    16.  } 

Source(s):
Category: Zoho :: Article: 794

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com

Related Articles

Joes Revolver Map

Accreditation

Badge - Certified Zoho Creator Associate
Badge - Certified Zoho Creator Associate

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF
© 2024 Joel Lipman .com. All Rights Reserved.