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 https://ascentbusiness.co.uk/zoho-services/uk-zoho-support.  For larger projects, talk to our experts and receive dedicated support from our hands-on project consultants at https://ascentbusiness.co.uk/zoho-services/zoho-crm-implementation.

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 https://ascentbusiness.co.uk.

Zoho CRM & Zoho Sign: Send CRM Merged Template for Zoho Sign

What?
I think I have a similar article on this website but the article below documents the full process to create a button that will map the values/fields from the record into a CRM Mail Merge template and send it off for signing, then return the Signed document attached to the initial record (where the button was).

Why?
If anyone has the envious task of sending a document out for signing by a customer, you will know this takes a while to do:
  1. You have to find the contact record in CRM for example
  2. Click on "Send for Zoho Sign"
  3. Select the template document or upload one
  4. Add the fields that you will complete, and the ones that other recipients will complete
  5. Send it for your end-user to complete and sign it
  6. This comes back to a section called "ZohoSign Documents", download the attachment
  7. Go back to the contact record, and upload the file to the attachment...
Some customers say this is unworkable and takes far too long, especially the part of adding the various field placeholders onto the template document; every time.

What if I told you we can code a single button on the record which does all of that for you; with only you needing to approve the document before it gets sent to the end user, and when they finish signing it, it attaches automatically back on to the record?... It is possible. The article below documents it but as this is a fair bit of coding, you may want to ask us to build this solution for you. It can take a few days but it will save your staff a ton of time per week!

What your staff see:
Zoho CRM & Zoho Sign: Send CRM Merged Template for Zoho Sign: The one button to rule them all

How?
So we're going to create a button. Let's use the example of a credit application you want to send to your customer, and when they complete it, the PDF version gets attached to their contact record... all from one click of a button:

Scenario 1: Credit Application: Template exists in ZohoSign

Very quickly, let's go over the implementation plan for this scenario (where you have the template in ZohoSign with fields for the end user to complete):
  1. Add code to button "Send Credit Application" off the contact record
  2. Sends ZohoSign template off to contact email
  3. Contact completes and signs the form via ZohoSign
  4. ZohoSign on completion triggers worklow from within ZohoCRM
  5. ZohoCRM webhook updates the tracking record (ZohoSign Document) and attaches the signed document to the contact record
Button Code:
copyraw
/* *******************************************************************************
Function:       String fn_SendCreditApplication(Int p_ContactID)
Label:          Fn - Contact - Send Credit Application
Trigger:        Off a button on the contact record
Purpose:	    Function used by a button off the contact record to send a credit application form for signing via ZohoSign
Inputs:         Contact Record ID
Outputs:        Attachment to the CRM record
				a ZohoSign Document record in CRM

Date Created:   2024-08-06 (Joel Lipman)
                - Initial release
                - Tidied up, removed commented code
                - Added Email and Account mapping to ZohoSign Document record
Date Modified:	2024-08-21 (Joel Lipman)
                - Creates a recipient and event record in CRM

More Information:
                https://www.zoho.com/deluge/help/sign/get-template-by-id.html

                Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all
******************************************************************************* */
//
// hardcoded template ID
// check URL https://sign.zoho.com/zs#/template/viewer/<template_id>
v_TemplateID = 12345000000678901;
v_Template = zoho.sign.getTemplateById(v_TemplateID);
v_OutputMessage = "";
//
// get the Action ID for SIGN
v_SendActionID = 0;
if(!isNull(v_Template) && !isNull(v_Template.get("templates")))
{
	// _
	if(!isNull(v_Template.get("templates").get("actions")))
	{
		// get the actions
		l_Actions = v_Template.get("templates").get("actions").toList();
		for each  m_Action in l_Actions
		{
			if(m_Action.get("action_type") == "SIGN")
			{
				v_SendActionId = m_Action.get("action_id").toLong();
			}
		}
	}
}
if(v_SendActionID == 0)
{
	v_OutputMessage = "Unable to find appropriate action. If this continues please contact support.";
}
//
// get contact record details
r_Contact = zoho.crm.getRecordById("Contacts",p_ContactID);
// info r_Contact;
//
// if email exists then create the ZohoSign Document record for tracking purposes
if(!isNull(r_Contact) && !isNull(r_Contact.get("Email")))
{
	v_RecipientEmail = r_Contact.get("Email");
	v_RecipientFullName = r_Contact.get("Full_Name");
	// _
	m_Parameters = Map();
	m_Data = Map();
	m_Templates = Map();
	m_Actions = Map();
	m_FieldData = Map();
	m_FieldTextData = Map();
	m_FieldBooleanData = Map();
	m_FieldDateData = Map();
	l_Actions = List();
	//
	m_FieldData.put("field_text_data",m_FieldTextData);
	m_FieldData.put("field_boolean_data",m_FieldBooleanData);
	m_FieldData.put("field_date_data",m_FieldDateData);
	m_Templates.put("field_data",m_FieldData);
	m_Actions.put("action_id",v_SendActionId);
	m_Actions.put("action_type","SIGN");
	m_Actions.put("private_notes","Please complete the following Credit Application Form or forward to the person within your company responsible for completing this.");
	m_Actions.put("recipient_phonenumber","");
	m_Actions.put("recipient_countrycode","");
	m_Actions.put("verify_recipient",false);
	m_Actions.put("recipient_name",v_RecipientFullName);
	m_Actions.put("recipient_email",v_RecipientEmail);
	l_Actions.add(m_Actions);
	// 
	m_Data.put("templates",m_Templates);
	m_Templates.put("actions",l_Actions);
	m_Templates.put("notes","");
	m_Parameters.put("data",m_Data);
	m_Parameters.put("is_quicksend",true);
	// 
	// Send the Zoho Sign Template with the pre-filled fields to the desired recipient/ recipients
	r_SignResponse = zoho.sign.createUsingTemplate(v_TemplateID,m_Parameters);
	// info r_SignResponse;
	//
	// get request and document IDs
	r_SignTemplate = zoho.sign.getTemplateById(v_TemplateID);
	v_TemplateName = r_SignTemplate.get("templates").get("template_name");
	// 
	// Get the Zoho Sign Document ID
	l_SignRequests = r_SignResponse.get("requests");
	l_DocumentIds = l_SignRequests.get("document_ids");
	// 
	//Get the Zoho Sign Request ID
	v_RequestId = l_SignRequests.get("request_id");
	m_DocInfo = l_DocumentIds.get(0);
	v_DocumentId = m_DocInfo.get("document_id");
	//
	// Create Map for CRM Record - ZohoSign Documents
	m_ZCrm_ZSDoc = Map();
	m_ZCrm_ZSDoc.put("Name",v_TemplateName);
	m_ZCrm_ZSDoc.put("zohosign__Contact",p_ContactID);
	m_ZCrm_ZSDoc.put("Email",v_RecipientEmail);
	if(!isNull(r_Contact.get("Account_Name")))
	{
		m_ZCrm_ZSDoc.put("zohosign__Account",r_Contact.get("Account_Name").get("id"));
	}
	m_ZCrm_ZSDoc.put("zohosign__Owner",r_Contact.get("Owner").get("id"));
	m_ZCrm_ZSDoc.put("zohosign__Date_Sent",today);
	m_ZCrm_ZSDoc.put("zohosign__ZohoSign_Document_ID",v_DocumentId);
	m_ZCrm_ZSDoc.put("zohosign__Module_Name","Contacts");
	m_ZCrm_ZSDoc.put("zohosign__Module_Record_ID",p_ContactID.toString());
	m_ZCrm_ZSDoc.put("zohosign__Document_Deadline",zoho.currentdate.addDay(15));
	m_ZCrm_ZSDoc.put("zohosign__Document_Status","Out for Signature");
	// Request_ID is a custom field added to retrieve the document later from ZohoWriter.
	m_ZCrm_ZSDoc.put("Request_ID",v_RequestId);
	r_ZCrm_ZSDoc = zoho.crm.createRecord("zohosign__ZohoSign_Documents",m_ZCrm_ZSDoc);
	v_OutputMessage = "Credit application sent to " + v_RecipientEmail + " for signing.";
	//
	// create CRM zohosign document child records (recipient and event)
	if(!isNull(r_ZCrm_ZSDoc.get("id")))
	{
		//
		// get logged in user (who clicked the button)
		v_LoggedInUserID = 0;
		r_Users = zoho.crm.getRecords("users");
		for each  m_User in r_Users.get("users")
		{
			if(m_User.get("email").equalsIgnoreCase(zoho.loginuserid))
			{
				v_LoggedInUserID = m_User.get("id");
			}
		}
		//
		// create CRM zohosign document recipient 
		m_CreateRecipient = Map();
		m_CreateRecipient.put("Name",v_RecipientFullName);
		m_CreateRecipient.put("Email",v_RecipientEmail);
		m_CreateRecipient.put("Owner",v_LoggedInUserID);
		m_CreateRecipient.put("zohosign__Recipient_Order",1);
		m_CreateRecipient.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"));
		m_CreateRecipient.put("zohosign__Recipient_Type","Signer");
		m_CreateRecipient.put("zohosign__Recipient_Status","Sent");
		m_CreateRecipient.put("zohosign__ZohoSign_Document_ID",v_DocumentId);
		r_CreateRecipient = zoho.crm.createRecord("zohosign__ZohoSign_Recipients",m_CreateRecipient);
		//
		// create CRM zohosign document event
		m_CreateEvent = Map();
		m_CreateEvent.put("Name",v_TemplateName + "-SENT");
		m_CreateEvent.put("Owner",v_LoggedInUserID);
		m_CreateEvent.put("Email",v_RecipientEmail);
		m_CreateEvent.put("zohosign__Date",zoho.currentdate);
		m_CreateEvent.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"));
		m_CreateEvent.put("zohosign__Description","Sent out for signature");
		r_CreateEvent = zoho.crm.createRecord("zohosign__ZohoSign_Document_Events",m_CreateEvent);
	}
}
//
// over to webhook to update this record and attach signed document to contact record
// see webhook: fn_ZohoSign_Webhook in CRM
return v_OutputMessage;
  1.  /* ******************************************************************************* 
  2.  Function:       string fn_SendCreditApplication(Int p_ContactID) 
  3.  Label:          Fn - Contact - Send Credit Application 
  4.  Trigger:        Off a button on the contact record 
  5.  Purpose:        Function used by a button off the contact record to send a credit application form for signing via ZohoSign 
  6.  Inputs:         Contact Record ID 
  7.  Outputs:        Attachment to the CRM record 
  8.                  a ZohoSign Document record in CRM 
  9.   
  10.  Date Created:   2024-08-06 (Joel Lipman) 
  11.                  - Initial release 
  12.                  - Tidied up, removed commented code 
  13.                  - Added Email and Account mapping to ZohoSign Document record 
  14.  Date Modified:    2024-08-21 (Joel Lipman) 
  15.                  - Creates a recipient and event record in CRM 
  16.   
  17.  More Information: 
  18.                  https://www.zoho.com/deluge/help/sign/get-template-by-id.html 
  19.   
  20.                  Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all 
  21.  ******************************************************************************* */ 
  22.  // 
  23.  // hardcoded template ID 
  24.  // check URL https://sign.zoho.com/zs#/template/viewer/<template_id> 
  25.  v_TemplateID = 12345000000678901
  26.  v_Template = zoho.sign.getTemplateById(v_TemplateID)
  27.  v_OutputMessage = ""
  28.  // 
  29.  // get the Action ID for SIGN 
  30.  v_SendActionID = 0
  31.  if(!isNull(v_Template) && !isNull(v_Template.get("templates"))) 
  32.  { 
  33.      // _ 
  34.      if(!isNull(v_Template.get("templates").get("actions"))) 
  35.      { 
  36.          // get the actions 
  37.          l_Actions = v_Template.get("templates").get("actions").toList()
  38.          for each  m_Action in l_Actions 
  39.          { 
  40.              if(m_Action.get("action_type") == "SIGN") 
  41.              { 
  42.                  v_SendActionId = m_Action.get("action_id").toLong()
  43.              } 
  44.          } 
  45.      } 
  46.  } 
  47.  if(v_SendActionID == 0) 
  48.  { 
  49.      v_OutputMessage = "Unable to find appropriate action. If this continues please contact support."
  50.  } 
  51.  // 
  52.  // get contact record details 
  53.  r_Contact = zoho.crm.getRecordById("Contacts",p_ContactID)
  54.  // info r_Contact; 
  55.  // 
  56.  // if email exists then create the ZohoSign Document record for tracking purposes 
  57.  if(!isNull(r_Contact) && !isNull(r_Contact.get("Email"))) 
  58.  { 
  59.      v_RecipientEmail = r_Contact.get("Email")
  60.      v_RecipientFullName = r_Contact.get("Full_Name")
  61.      // _ 
  62.      m_Parameters = Map()
  63.      m_Data = Map()
  64.      m_Templates = Map()
  65.      m_Actions = Map()
  66.      m_FieldData = Map()
  67.      m_FieldTextData = Map()
  68.      m_FieldBooleanData = Map()
  69.      m_FieldDateData = Map()
  70.      l_Actions = List()
  71.      // 
  72.      m_FieldData.put("field_text_data",m_FieldTextData)
  73.      m_FieldData.put("field_boolean_data",m_FieldBooleanData)
  74.      m_FieldData.put("field_date_data",m_FieldDateData)
  75.      m_Templates.put("field_data",m_FieldData)
  76.      m_Actions.put("action_id",v_SendActionId)
  77.      m_Actions.put("action_type","SIGN")
  78.      m_Actions.put("private_notes","Please complete the following Credit Application Form or forward to the person within your company responsible for completing this.")
  79.      m_Actions.put("recipient_phonenumber","")
  80.      m_Actions.put("recipient_countrycode","")
  81.      m_Actions.put("verify_recipient",false)
  82.      m_Actions.put("recipient_name",v_RecipientFullName)
  83.      m_Actions.put("recipient_email",v_RecipientEmail)
  84.      l_Actions.add(m_Actions)
  85.      // 
  86.      m_Data.put("templates",m_Templates)
  87.      m_Templates.put("actions",l_Actions)
  88.      m_Templates.put("notes","")
  89.      m_Parameters.put("data",m_Data)
  90.      m_Parameters.put("is_quicksend",true)
  91.      // 
  92.      // Send the Zoho Sign Template with the pre-filled fields to the desired recipient/ recipients 
  93.      r_SignResponse = zoho.sign.createUsingTemplate(v_TemplateID,m_Parameters)
  94.      // info r_SignResponse; 
  95.      // 
  96.      // get request and document IDs 
  97.      r_SignTemplate = zoho.sign.getTemplateById(v_TemplateID)
  98.      v_TemplateName = r_SignTemplate.get("templates").get("template_name")
  99.      // 
  100.      // Get the Zoho Sign Document ID 
  101.      l_SignRequests = r_SignResponse.get("requests")
  102.      l_DocumentIds = l_SignRequests.get("document_ids")
  103.      // 
  104.      //Get the Zoho Sign Request ID 
  105.      v_RequestId = l_SignRequests.get("request_id")
  106.      m_DocInfo = l_DocumentIds.get(0)
  107.      v_DocumentId = m_DocInfo.get("document_id")
  108.      // 
  109.      // Create Map for CRM Record - ZohoSign Documents 
  110.      m_ZCrm_ZSDoc = Map()
  111.      m_ZCrm_ZSDoc.put("Name",v_TemplateName)
  112.      m_ZCrm_ZSDoc.put("zohosign__Contact",p_ContactID)
  113.      m_ZCrm_ZSDoc.put("Email",v_RecipientEmail)
  114.      if(!isNull(r_Contact.get("Account_Name"))) 
  115.      { 
  116.          m_ZCrm_ZSDoc.put("zohosign__Account",r_Contact.get("Account_Name").get("id"))
  117.      } 
  118.      m_ZCrm_ZSDoc.put("zohosign__Owner",r_Contact.get("Owner").get("id"))
  119.      m_ZCrm_ZSDoc.put("zohosign__Date_Sent",today)
  120.      m_ZCrm_ZSDoc.put("zohosign__ZohoSign_Document_ID",v_DocumentId)
  121.      m_ZCrm_ZSDoc.put("zohosign__Module_Name","Contacts")
  122.      m_ZCrm_ZSDoc.put("zohosign__Module_Record_ID",p_ContactID.toString())
  123.      m_ZCrm_ZSDoc.put("zohosign__Document_Deadline",zoho.currentdate.addDay(15))
  124.      m_ZCrm_ZSDoc.put("zohosign__Document_Status","Out for Signature")
  125.      // Request_ID is a custom field added to retrieve the document later from ZohoWriter. 
  126.      m_ZCrm_ZSDoc.put("Request_ID",v_RequestId)
  127.      r_ZCrm_ZSDoc = zoho.crm.createRecord("zohosign__ZohoSign_Documents",m_ZCrm_ZSDoc)
  128.      v_OutputMessage = "Credit application sent to " + v_RecipientEmail + " for signing."
  129.      // 
  130.      // create CRM zohosign document child records (recipient and event) 
  131.      if(!isNull(r_ZCrm_ZSDoc.get("id"))) 
  132.      { 
  133.          // 
  134.          // get logged in user (who clicked the button) 
  135.          v_LoggedInUserID = 0
  136.          r_Users = zoho.crm.getRecords("users")
  137.          for each  m_User in r_Users.get("users") 
  138.          { 
  139.              if(m_User.get("email").equalsIgnoreCase(zoho.loginuserid)) 
  140.              { 
  141.                  v_LoggedInUserID = m_User.get("id")
  142.              } 
  143.          } 
  144.          // 
  145.          // create CRM zohosign document recipient 
  146.          m_CreateRecipient = Map()
  147.          m_CreateRecipient.put("Name",v_RecipientFullName)
  148.          m_CreateRecipient.put("Email",v_RecipientEmail)
  149.          m_CreateRecipient.put("Owner",v_LoggedInUserID)
  150.          m_CreateRecipient.put("zohosign__Recipient_Order",1)
  151.          m_CreateRecipient.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"))
  152.          m_CreateRecipient.put("zohosign__Recipient_Type","Signer")
  153.          m_CreateRecipient.put("zohosign__Recipient_Status","Sent")
  154.          m_CreateRecipient.put("zohosign__ZohoSign_Document_ID",v_DocumentId)
  155.          r_CreateRecipient = zoho.crm.createRecord("zohosign__ZohoSign_Recipients",m_CreateRecipient)
  156.          // 
  157.          // create CRM zohosign document event 
  158.          m_CreateEvent = Map()
  159.          m_CreateEvent.put("Name",v_TemplateName + "-SENT")
  160.          m_CreateEvent.put("Owner",v_LoggedInUserID)
  161.          m_CreateEvent.put("Email",v_RecipientEmail)
  162.          m_CreateEvent.put("zohosign__Date",zoho.currentdate)
  163.          m_CreateEvent.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"))
  164.          m_CreateEvent.put("zohosign__Description","Sent out for signature")
  165.          r_CreateEvent = zoho.crm.createRecord("zohosign__ZohoSign_Document_Events",m_CreateEvent)
  166.      } 
  167.  } 
  168.  // 
  169.  // over to webhook to update this record and attach signed document to contact record 
  170.  // see webhook: fn_ZohoSign_Webhook in CRM 
  171.  return v_OutputMessage; 

On Sign Completion: Attach the signed document to the contact record
[DEPRECATED: Please use Generic Webhook at the bottom of this page]
For this, we'll create the webhook function first (this one I've called Fn - ZohoSign - Document Completed) and then make it into a REST API function for ZohoSign to send the data to:
copyraw
/* *******************************************************************************
Function:       String fn_ZohoSign_DocumentCompleted(string crmAPIRequest)
Label:          Fn - ZohoSign - Document Completed
Trigger:        Webhook when ZohoSign sees a completed document
Purpose:	    This function is used as a webhook when ZohoSign detects a document has been completed
Inputs:         crmAPIRequest
Outputs:        crmAPIResponse

Date Created:   2024-08-06 (Joel Lipman)
                - Initial release
                - Modified to attach the signed document to the originating Contact Record

More Information:
                https://www.zoho.com/deluge/help/sign-tasks.html
				https://www.zoho.com/deluge/help/sign/get-document-by-id.html
				https://www.zoho.com/deluge/help/sign/download-document.html

				Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all
******************************************************************************* */
//
// crmAPIRequest
v_DebugMessage = "";
v_RequestID = 0;
m_Body = ifnull(crmAPIRequest.get("body"),Map());
v_DebugMessage = "Body:<br /><br />" + m_Body + "<hr />";
//
// determine type of document done
if(!isNull(m_Body.get("requests")))
{
	v_RequestName = ifnull(m_Body.get("requests").get("request_name"),"-");
	if(v_RequestName.containsIgnoreCase("credit application"))
	{
		v_ZohoSign_DocumentType = "CreditApplication";
	}
	v_DebugMessage = v_DebugMessage + "Request Type: <br /><br />" + v_ZohoSign_DocumentType + "<hr />";
}
//
// update the ZohoSign Document record for templates in ZohoSign 
// NB: if your document is NOT in ZohoSign but is a mail merge template in CRM, then skip this if statement
if(v_ZohoSign_DocumentType == "CreditApplication")
{
	v_RequestID = ifnull(m_Body.get("requests").get("request_id"),"0").toLong();
	l_SearchResults = zoho.crm.searchRecords("zohosign__ZohoSign_Documents","Request_ID:equals:" + v_RequestID);
	v_DebugMessage = v_DebugMessage + "Search Result(s): <br /><br />" + l_SearchResults + "<hr />";
	for each  m_ZS_Doc in l_SearchResults
	{
		if(m_ZS_Doc.get("Request_ID") == v_RequestID)
		{
			// updates the CRM ZohoSign Document Record
			v_ZS_DocRecordID = m_ZS_Doc.get("id");
			m_SignDocument = zoho.sign.getDocumentById(v_RequestID);
			v_SignDate = m_SignDocument.get("requests").get("action_time");
			v_SignDate = v_SignDate.toDate("yyyy-MM-dd");
			r_Update = zoho.crm.updateRecord("zohosign__ZohoSign_Documents",v_ZS_DocRecordID,{"zohosign__Document_Status":"SIGNED","zohosign__Date_Completed":v_SignDate});
			v_DebugMessage = v_DebugMessage + "ZS Doc Update: <br /><br />" + r_Update + "<hr />";
		}
	}
	//
	// now attach to CRM Contact
	if(v_RequestID != 0)
	{
		r_ZS_DocumentDetails = zoho.sign.getDocumentById(v_RequestID);
		m_Requests = ifnull(r_ZS_DocumentDetails.get("requests"),Map());
		l_Actions = ifnull(m_Requests.get("actions"),List());
		for each  m_Action in l_Actions
		{
			if(!isNull(m_Action.get("recipient_email")))
			{
				info "Action Type: " + m_Action.get("action_type");
				info "Action ID: " + m_Action.get("action_id");
				info "Action Status: " + m_Action.get("action_status");
				//
				// match to contact in CRM
				l_ContactResults = zoho.crm.searchRecords("Contacts","Email:equals:" + m_Action.get("recipient_email"));
				for each  m_Contact in l_ContactResults
				{
					if(m_Contact.get("Email").equalsIgnoreCase(m_Action.get("recipient_email")))
					{
						r_ZS_Download = zoho.sign.downloadDocument(v_RequestID);
						r_ZS_Download.setFileName(m_Requests.get("request_name") + "_SIGNED.pdf");
						r_AttachFile = zoho.crm.attachFile("Contacts",m_Contact.get("id"),r_ZS_Download);
						if(!isNull(r_AttachFile.get("message")))
						{
							v_DebugMessage = v_DebugMessage + "CRM Attachment: <br /><br />" + r_AttachFile.get("message") + "<hr />";
						}
					}
				}
			}
		}
	}
}
v_DebugMessage = v_DebugMessage + "Date/Time: <br /><br />" + zoho.currenttime;
//
return "Webhook received";
  1.  /* ******************************************************************************* 
  2.  Function:       string fn_ZohoSign_DocumentCompleted(string crmAPIRequest) 
  3.  Label:          Fn - ZohoSign - Document Completed 
  4.  Trigger:        Webhook when ZohoSign sees a completed document 
  5.  Purpose:        This function is used as a webhook when ZohoSign detects a document has been completed 
  6.  Inputs:         crmAPIRequest 
  7.  Outputs:        crmAPIResponse 
  8.   
  9.  Date Created:   2024-08-06 (Joel Lipman) 
  10.                  - Initial release 
  11.                  - Modified to attach the signed document to the originating Contact Record 
  12.   
  13.  More Information: 
  14.                  https://www.zoho.com/deluge/help/sign-tasks.html 
  15.                  https://www.zoho.com/deluge/help/sign/get-document-by-id.html 
  16.                  https://www.zoho.com/deluge/help/sign/download-document.html 
  17.   
  18.                  Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all 
  19.  ******************************************************************************* */ 
  20.  // 
  21.  // crmAPIRequest 
  22.  v_DebugMessage = ""
  23.  v_RequestID = 0
  24.  m_Body = ifnull(crmAPIRequest.get("body"),Map())
  25.  v_DebugMessage = "Body:<br /><br />" + m_Body + "<hr />"
  26.  // 
  27.  // determine type of document done 
  28.  if(!isNull(m_Body.get("requests"))) 
  29.  { 
  30.      v_RequestName = ifnull(m_Body.get("requests").get("request_name"),"-")
  31.      if(v_RequestName.containsIgnoreCase("credit application")) 
  32.      { 
  33.          v_ZohoSign_DocumentType = "CreditApplication"
  34.      } 
  35.      v_DebugMessage = v_DebugMessage + "Request type: <br /><br />" + v_ZohoSign_DocumentType + "<hr />"
  36.  } 
  37.  // 
  38.  // update the ZohoSign Document record for templates in ZohoSign 
  39.  // NB: if your document is NOT in ZohoSign but is a mail merge template in CRM, then skip this if statement 
  40.  if(v_ZohoSign_DocumentType == "CreditApplication") 
  41.  { 
  42.      v_RequestID = ifnull(m_Body.get("requests").get("request_id"),"0").toLong()
  43.      l_SearchResults = zoho.crm.searchRecords("zohosign__ZohoSign_Documents","Request_ID:equals:" + v_RequestID)
  44.      v_DebugMessage = v_DebugMessage + "Search Result(s): <br /><br />" + l_SearchResults + "<hr />"
  45.      for each  m_ZS_Doc in l_SearchResults 
  46.      { 
  47.          if(m_ZS_Doc.get("Request_ID") == v_RequestID) 
  48.          { 
  49.              // updates the CRM ZohoSign Document Record 
  50.              v_ZS_DocRecordID = m_ZS_Doc.get("id")
  51.              m_SignDocument = zoho.sign.getDocumentById(v_RequestID)
  52.              v_SignDate = m_SignDocument.get("requests").get("action_time")
  53.              v_SignDate = v_SignDate.toDate("yyyy-MM-dd")
  54.              r_Update = zoho.crm.updateRecord("zohosign__ZohoSign_Documents",v_ZS_DocRecordID,{"zohosign__Document_Status":"SIGNED","zohosign__Date_Completed":v_SignDate})
  55.              v_DebugMessage = v_DebugMessage + "ZS Doc Update: <br /><br />" + r_Update + "<hr />"
  56.          } 
  57.      } 
  58.      // 
  59.      // now attach to CRM Contact 
  60.      if(v_RequestID != 0) 
  61.      { 
  62.          r_ZS_DocumentDetails = zoho.sign.getDocumentById(v_RequestID)
  63.          m_Requests = ifnull(r_ZS_DocumentDetails.get("requests"),Map())
  64.          l_Actions = ifnull(m_Requests.get("actions"),List())
  65.          for each  m_Action in l_Actions 
  66.          { 
  67.              if(!isNull(m_Action.get("recipient_email"))) 
  68.              { 
  69.                  info "Action type: " + m_Action.get("action_type")
  70.                  info "Action ID: " + m_Action.get("action_id")
  71.                  info "Action Status: " + m_Action.get("action_status")
  72.                  // 
  73.                  // match to contact in CRM 
  74.                  l_ContactResults = zoho.crm.searchRecords("Contacts","Email:equals:" + m_Action.get("recipient_email"))
  75.                  for each  m_Contact in l_ContactResults 
  76.                  { 
  77.                      if(m_Contact.get("Email").equalsIgnoreCase(m_Action.get("recipient_email"))) 
  78.                      { 
  79.                          r_ZS_Download = zoho.sign.downloadDocument(v_RequestID)
  80.                          r_ZS_Download.setFileName(m_Requests.get("request_name") + "_SIGNED.pdf")
  81.                          r_AttachFile = zoho.crm.attachFile("Contacts",m_Contact.get("id"),r_ZS_Download)
  82.                          if(!isNull(r_AttachFile.get("message"))) 
  83.                          { 
  84.                              v_DebugMessage = v_DebugMessage + "CRM Attachment: <br /><br />" + r_AttachFile.get("message") + "<hr />"
  85.                          } 
  86.                      } 
  87.                  } 
  88.              } 
  89.          } 
  90.      } 
  91.  } 
  92.  v_DebugMessage = v_DebugMessage + "Date/Time: <br /><br />" + zoho.currenttime
  93.  // 
  94.  return "Webhook received"

Setup the webhook in ZohoSign
Now we need the webhook added to ZohoSign so that when a document is completed, it tells ZohoCRM. First, let's create an endpoint for the webhook in ZohoCRM:
  1. Login to ZohoCRM > Setup > Functions > and hover the mouse over the function you just created above (eg. Fn - ZohoSign - Document Completed)
  2. Hover the mouse over the ellipsis that appears and select "REST API"
    Zoho CRM & Zoho Sign: Send CRM Merged Template for Zoho Sign:
  3. Then click on the API switch to enable it and copy the resulting URL to your clipboard
    Zoho CRM & Zoho Sign: Send CRM Merged Template for Zoho Sign:
  4. Now login to ZohoSign
  5. In the left sidebar, go to > Settings > Developer Settings > Webhooks
  6. Copy and paste the URL and set the callback events (triggers) to "Completed by all"
    Zoho CRM & Zoho Sign: Send CRM Merged Template for Zoho Sign:
  7. Now whenever a document is completed in ZohoSign, it will send the data of the request to ZohoCRM

Done!
So the above is good for a credit application form where the template exists in Zoho Sign. But now how do we do this using simply a Mail Merge template held in ZohoCRM? Let's give it a go:

Scenario 2: Custom Module Mail Merge: Template exists in ZohoCRM

For this scenario, the use-case is a little more complex:
  1. Login to ZohoCRM, go to custom module, in this case "Maintenance"
  2. Click on Button to send a document for the engineer to complete
    1. Engineer to complete notes / description / additional info
    2. Engineer to sign
  3. Then the ZohoSign document gets sent to a line manager to Approve the document
  4. Then the document gets sent to the end user
Additional requirements:
  • The signed version gets attached to the custom module record
  • a ZohoSign Document record needs to be created to track the progress of the document. I know this exists in ZohoSign already but we don't expect staff to go to another app to check on this and would rather see it directly in ZohoCRM on the relevant record.

So the more observant among you will have noted that I'm recording the module name and module record ID on the ZohoSign Document record. Doing this at the point of the request means that I can have a generic webhook that handles every ZohoSign completion and attach the signed PDF accordingly.

The button for the custom module
For this I go the custom module, in this example called "Maintenance", and add a button:
copyraw
/* *******************************************************************************
Function:       String fn_MailMerge_Maintenance(String p_MaintenanceID)
Label:          Fn - Mail Merge - Maintenance
Trigger:        Off a button on the maintenance record
Purpose:	    Function used by button off maintenance record to send a Maintenance Report to the customer contact.
Inputs:         Record ID
Outputs:        Attachment to the CRM record

Date Created:   2024-08-07 (Joel Lipman)
                - Initial release
Date Modified:	2024-08-22 (Joel Lipman)
                - Creates ZohoSign Document records in ZohoCRM (for tracking progress of e-sign process)

More Information:
                https://www.joellipman.com/articles/crm/zoho/zoho-crm-zoho-writer-button-to-merge-template,-send,-and-attach.html
				https://www.zoho.com/deluge/help/writer/merge-and-sign.html
				https://www.zoho.com/deluge/help/script/sign/create-using-template.html
				
				ErrorCode: R6006
				Message: Invalid signer info. Please try again with proper signer info.
				Solution: Recipients list was incorrect and I'd assigned the recipient twice.
				
				Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all

				Also note that the document already exists as a mail merge template in CRM.
******************************************************************************* */
//
b_Debug = true;
v_ModuleName = "Maintenance";
v_OutputMessage = "";
r_RecordDetails = zoho.crm.getRecordById(v_ModuleName,p_MaintenanceID);
//info r_RecordDetails;
v_AttachmentDate = zoho.currentdate.toString("dd-MMM-yyyy");
v_SignType = "Maintenance Report";
v_FileNameWithouExt = v_SignType;
//
// Specify template ID
// check URL https://sign.zoho.com/zs#/template/viewer/<template_id>
v_TemplateID = "abcdefghijklmnopqrstuvwxyz01234567890";
// 
// get merge field names (for reference - not used later in this fuction) 
// r_Fields = zoho.writer.getMergeFields(v_TemplateID,"my_writer_connection");
//info r_Fields;
// 
// map data to these fields 
m_Data = Map();
//
// get reference number for filename
if(!isNull(r_RecordDetails.get("Name")))
{
	v_SetFilename = v_SignType + " " + r_RecordDetails.get("Name");
}
//
// get maintenance record item lookup and map values
if(!isNull(r_RecordDetails.get("Lookup_Serial_Number")))
{
	r_CustomerAsset = zoho.crm.getRecordById("Assets",r_RecordDetails.get("Lookup_Serial_Number").get("id"));
	if(!isNull(r_CustomerAsset.get("Account_Name")))
	{
		m_Data.put("Lookup_Serial_Number.Account_Name",r_CustomerAsset.get("Account_Name").get("name"));
	}
	// .. MAP ALL OTHER FIELDS
}
//
// get customer contact record
v_EndCustomerFullName = "";
v_EndCustomerEmail = "";
v_EndCustomerAccountID = 0;
v_EndCustomerContactID = 0;
if(!isNull(r_RecordDetails.get("Customer_Contact")))
{
	v_EndCustomerContactID = r_RecordDetails.get("Customer_Contact").get("id").toLong();
	r_CustomerContact = zoho.crm.getRecordById("Contacts",v_EndCustomerContactID);
	if(!isNull(r_CustomerContact.get("Full_Name")))
	{
		v_EndCustomerFullName = r_CustomerContact.get("Full_Name");
		m_Data.put("Customer_Contact.Full_Name",v_EndCustomerFullName);
	}
	if(!isNull(r_CustomerContact.get("Phone")))
	{
		m_Data.put("Customer_Contact.Phone",r_CustomerContact.get("Phone"));
	}
	if(!isNull(r_CustomerContact.get("Email")))
	{
		v_EndCustomerEmail = r_CustomerContact.get("Email");
		m_Data.put("Customer_Contact.Email",v_EndCustomerEmail);
	}
	if(!isNull(r_CustomerContact.get("Account_Name")))
	{
		v_EndCustomerAccountID = r_CustomerContact.get("Account_Name").get("id").toLong();
	}
}
//
// get organization phone number
m_OrgDetails = Map();
r_Response = zoho.crm.invokeConnector("crm.getorg",m_OrgDetails);
if(!isnull(r_Response.get("status_code")))
{
	if(r_Response.get("status_code") == 200)
	{
		l_OrgDetails = r_Response.get("response").get("org");
		for each  r_Org in l_OrgDetails
		{
			m_Data.put("org.phone",r_Org.get("phone"));
		}
	}
}
//
// map other values from this record
v_OwnerID = "0";
if(!isNull(r_RecordDetails.get("Attending_Engineer")))
{
	m_Data.put("Attending_Engineer",r_RecordDetails.get("Attending_Engineer").get("name"));
}
if(!isNull(r_RecordDetails.get("Service_Start_Time")))
{
	m_Data.put("Service_Start_Time",r_RecordDetails.get("Service_Start_Time").replaceAll("T"," ",true).getPrefix("+").toTime().toString("dd-MMM-yyyy HH:mm","Europe/London"));
}
if(!isNull(r_RecordDetails.get("Service_End_Time")))
{
	m_Data.put("Service_End_Time",r_RecordDetails.get("Service_End_Time").replaceAll("T"," ",true).getPrefix("+").toTime().toString("dd-MMM-yyyy HH:mm","Europe/London"));
}
// ... MAP FURTHER VALUES
// 
// merge the data into the template (for ZohoWriter)
m_MergedData = Map();
m_MergedData.put("merge_data",{"data":m_Data});
//info m_MergedData;
//
// ***************************************
// default first recipient to record owner
l_Recipients = List();
v_EngineerID = r_RecordDetails.get("Owner").get("id");
v_EngineerFullName = r_RecordDetails.get("Owner").get("name");
v_EngineerEmail = r_RecordDetails.get("Owner").get("email");
//
// get first recipient (engineer)
if(!isNull(r_RecordDetails.get("Attending_Engineer")))
{
	v_User_EngineerID = r_RecordDetails.get("Attending_Engineer").get("id");
	r_Users = zoho.crm.getRecords("users");
	for each  m_User in r_Users.get("users")
	{
		if(m_User.get("id").equalsIgnoreCase(v_User_EngineerID))
		{
			v_EngineerID = m_User.get("id");
			v_EngineerFullName = m_User.get("full_name");
			v_EngineerEmail = m_User.get("email");
		}
		//
		// get manager approver while we're here (where manager name = Joel Lipman)
		if(m_User.get("full_name").equalsIgnoreCase("Joel Lipman"))
		{
			v_ManagerID = m_User.get("id");
			v_ManagerFullName = m_User.get("full_name");
			v_ManagerEmail = m_User.get("email");
		}
	}
}
//
// OVERRIDES: for testing purposes
if(b_Debug)
{
	v_EngineerFullName = "Engineering Joel";
	v_EngineerEmail = "This email address is being protected from spambots. You need JavaScript enabled to view it.";
	v_ManagerFullName = "Approving Joel";
	v_ManagerEmail = "This email address is being protected from spambots. You need JavaScript enabled to view it.";
	v_EndCustomerFullName = "Customer Joel";
	v_EndCustomerEmail = "This email address is being protected from spambots. You need JavaScript enabled to view it.";
}
//
// engineer recipient (job notes to add - A4 size)
m_RecipientSigner = Map();
m_RecipientSigner.put("recipient_1",v_EngineerEmail);
m_RecipientSigner.put("recipient_name",v_EngineerFullName);
m_RecipientSigner.put("action_type","sign");
m_RecipientSigner.put("private_notes","Please complete and sign the document before it is sent for manager approval: " + v_ManagerFullName);
l_Recipients.add(m_RecipientSigner);
//
// manager recipient (approver)
m_RecipientCopy = Map();
m_RecipientCopy.put("recipient_2",v_ManagerEmail);
m_RecipientCopy.put("recipient_name",v_ManagerFullName);
m_RecipientCopy.put("action_type","approve");
m_RecipientCopy.put("private_notes","The engineer has completed this document.  Please check and approve this document before I send this to the customer: " + v_EndCustomerFullName);
l_Recipients.add(m_RecipientCopy);
//
// end user recipient (approver)
m_RecipientCopy = Map();
m_RecipientCopy.put("recipient_3",v_EndCustomerEmail);
m_RecipientCopy.put("recipient_name",v_EndCustomerFullName);
m_RecipientCopy.put("action_type","approve");
m_RecipientCopy.put("private_notes","Please read and confirm you approve of this document.  You will receive a copy for your records once completed.");
l_Recipients.add(m_RecipientCopy);
//info l_Recipients;
//
// some signing options
v_DaysUntilExpiry = 3;
m_Options = Map();
m_Options.put("sign_in_order","true");
m_Options.put("set_expire",v_DaysUntilExpiry.toString());
m_Options.put("reminder_period",v_DaysUntilExpiry);
//
// send for signature
v_Filename = v_FileNameWithouExt + ".pdf";
r_ZohoSignRequest = zoho.writer.mergeAndSign(v_TemplateID,m_MergedData,v_Filename,l_Recipients,m_Options,"my_writer_connection");
// info r_ZohoSignRequest;
//
// track in CRM Zoho Sign (need request ID)
l_ZohoSignResponses = ifnull(r_ZohoSignRequest.get("records"),List());
for each  m_ZohoSignResponse in l_ZohoSignResponses
{
	if(!isNull(m_ZohoSignResponse.get("sign_request_id")))
	{
		//
		// Create CRM Record ZohoSign Documents for tracking progress
		m_ZCrm_ZSDoc = Map();
		m_ZCrm_ZSDoc.put("Name",v_FileNameWithouExt);
		m_ZCrm_ZSDoc.put("zohosign__Contact",v_EndCustomerContactID);
		m_ZCrm_ZSDoc.put("Email",v_EndCustomerEmail);
		if(v_EndCustomerAccountID != 0)
		{
			m_ZCrm_ZSDoc.put("zohosign__Account",v_EndCustomerAccountID);
		}
		m_ZCrm_ZSDoc.put("zohosign__Owner",v_ZCRM_DocOwnerID);
		m_ZCrm_ZSDoc.put("zohosign__Date_Sent",zoho.currentdate);
		m_ZCrm_ZSDoc.put("zohosign__Module_Name",v_ModuleName);
		m_ZCrm_ZSDoc.put("zohosign__Module_Record_ID",p_MaintenanceID.toString());
		m_ZCrm_ZSDoc.put("zohosign__Document_Deadline",zoho.currentdate.addDay(v_DaysUntilExpiry));
		m_ZCrm_ZSDoc.put("zohosign__Document_Status","Out for Signature");
		// Request_ID is a custom field added to retrieve the document later from ZohoWriter.
		m_ZCrm_ZSDoc.put("Request_ID",m_ZohoSignResponse.get("sign_request_id"));
		r_ZCrm_ZSDoc = zoho.crm.createRecord("zohosign__ZohoSign_Documents",m_ZCrm_ZSDoc);
		//
		// create CRM zohosign document child records (recipient and event)
		if(!isNull(r_ZCrm_ZSDoc.get("id")))
		{
			//
			// get logged in user (who clicked the button)
			v_LoggedInUserID = 0;
			r_Users = zoho.crm.getRecords("users");
			for each  m_User in r_Users.get("users")
			{
				if(m_User.get("email").equalsIgnoreCase(zoho.loginuserid))
				{
					v_LoggedInUserID = m_User.get("id");
				}
			}
			//
			// create CRM zohosign document recipient(s)
			v_RecipientIndex = 0;
			for each  m_Recipient in l_Recipients
			{
				v_RecipientIndex = v_RecipientIndex + 1;
				m_CreateRecipient = Map();
				m_CreateRecipient.put("Name",m_Recipient.get("recipient_name"));
				m_CreateRecipient.put("Email",m_Recipient.get("recipient_" + v_RecipientIndex));
				m_CreateRecipient.put("Owner",v_LoggedInUserID);
				m_CreateRecipient.put("zohosign__Recipient_Order",v_RecipientIndex);
				m_CreateRecipient.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"));
				v_RecipientType = if(m_Recipient.get("action_type") == "sign","Signer","Approver");
				m_CreateRecipient.put("zohosign__Recipient_Type",v_RecipientType);
				m_CreateRecipient.put("zohosign__Recipient_Status","Sent");
				r_CreateRecipient = zoho.crm.createRecord("zohosign__ZohoSign_Recipients",m_CreateRecipient);
			}
			//
			// create CRM zohosign document event (1st to engineer)
			m_CreateEvent = Map();
			m_CreateEvent.put("Name",v_SignType + "-SENT");
			m_CreateEvent.put("Owner",v_LoggedInUserID);
			m_CreateEvent.put("Email",zoho.loginuserid);
			m_CreateEvent.put("zohosign__Date",zoho.currentdate);
			m_CreateEvent.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"));
			m_CreateEvent.put("zohosign__Description","Sent out for signature");
			r_CreateEvent = zoho.crm.createRecord("zohosign__ZohoSign_Document_Events",m_CreateEvent);
		}
		//
		// output result to user
		v_OutputMessage = v_SignType + " sent to " + v_EngineerFullName + " (" + v_EngineerEmail + ") for signing.";
	}
}
// 
// return response to user 
return v_OutputMessage;
  1.  /* ******************************************************************************* 
  2.  Function:       string fn_MailMerge_Maintenance(String p_MaintenanceID) 
  3.  Label:          Fn - Mail Merge - Maintenance 
  4.  Trigger:        Off a button on the maintenance record 
  5.  Purpose:        Function used by button off maintenance record to send a Maintenance Report to the customer contact. 
  6.  Inputs:         Record ID 
  7.  Outputs:        Attachment to the CRM record 
  8.   
  9.  Date Created:   2024-08-07 (Joel Lipman) 
  10.                  - Initial release 
  11.  Date Modified:    2024-08-22 (Joel Lipman) 
  12.                  - Creates ZohoSign Document records in ZohoCRM (for tracking progress of e-sign process) 
  13.   
  14.  More Information: 
  15.                  https://www.joellipman.com/articles/crm/zoho/zoho-crm-zoho-writer-button-to-merge-template,-send,-and-attach.html 
  16.                  https://www.zoho.com/deluge/help/writer/merge-and-sign.html 
  17.                  https://www.zoho.com/deluge/help/script/sign/create-using-template.html 
  18.   
  19.                  ErrorCode: R6006 
  20.                  message: Invalid signer info. Please try again with proper signer info. 
  21.                  Solution: Recipients list was incorrect and I'd assigned the recipient twice. 
  22.   
  23.                  Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all 
  24.   
  25.                  Also note that the document already exists as a mail merge template in CRM. 
  26.  ******************************************************************************* */ 
  27.  // 
  28.  b_Debug = true
  29.  v_ModuleName = "Maintenance"
  30.  v_OutputMessage = ""
  31.  r_RecordDetails = zoho.crm.getRecordById(v_ModuleName,p_MaintenanceID)
  32.  //info r_RecordDetails; 
  33.  v_AttachmentDate = zoho.currentdate.toString("dd-MMM-yyyy")
  34.  v_SignType = "Maintenance Report"
  35.  v_FileNameWithouExt = v_SignType; 
  36.  // 
  37.  // Specify template ID 
  38.  // check URL https://sign.zoho.com/zs#/template/viewer/<template_id> 
  39.  v_TemplateID = "abcdefghijklmnopqrstuvwxyz01234567890"
  40.  // 
  41.  // get merge field names (for reference - not used later in this fuction) 
  42.  // r_Fields = zoho.writer.getMergeFields(v_TemplateID,"my_writer_connection")
  43.  //info r_Fields; 
  44.  // 
  45.  // map data to these fields 
  46.  m_Data = Map()
  47.  // 
  48.  // get reference number for filename 
  49.  if(!isNull(r_RecordDetails.get("Name"))) 
  50.  { 
  51.      v_SetFilename = v_SignType + " " + r_RecordDetails.get("Name")
  52.  } 
  53.  // 
  54.  // get maintenance record item lookup and map values 
  55.  if(!isNull(r_RecordDetails.get("Lookup_Serial_Number"))) 
  56.  { 
  57.      r_CustomerAsset = zoho.crm.getRecordById("Assets",r_RecordDetails.get("Lookup_Serial_Number").get("id"))
  58.      if(!isNull(r_CustomerAsset.get("Account_Name"))) 
  59.      { 
  60.          m_Data.put("Lookup_Serial_Number.Account_Name",r_CustomerAsset.get("Account_Name").get("name"))
  61.      } 
  62.      // .. MAP ALL OTHER FIELDS 
  63.  } 
  64.  // 
  65.  // get customer contact record 
  66.  v_EndCustomerFullName = ""
  67.  v_EndCustomerEmail = ""
  68.  v_EndCustomerAccountID = 0
  69.  v_EndCustomerContactID = 0
  70.  if(!isNull(r_RecordDetails.get("Customer_Contact"))) 
  71.  { 
  72.      v_EndCustomerContactID = r_RecordDetails.get("Customer_Contact").get("id").toLong()
  73.      r_CustomerContact = zoho.crm.getRecordById("Contacts",v_EndCustomerContactID)
  74.      if(!isNull(r_CustomerContact.get("Full_Name"))) 
  75.      { 
  76.          v_EndCustomerFullName = r_CustomerContact.get("Full_Name")
  77.          m_Data.put("Customer_Contact.Full_Name",v_EndCustomerFullName)
  78.      } 
  79.      if(!isNull(r_CustomerContact.get("Phone"))) 
  80.      { 
  81.          m_Data.put("Customer_Contact.Phone",r_CustomerContact.get("Phone"))
  82.      } 
  83.      if(!isNull(r_CustomerContact.get("Email"))) 
  84.      { 
  85.          v_EndCustomerEmail = r_CustomerContact.get("Email")
  86.          m_Data.put("Customer_Contact.Email",v_EndCustomerEmail)
  87.      } 
  88.      if(!isNull(r_CustomerContact.get("Account_Name"))) 
  89.      { 
  90.          v_EndCustomerAccountID = r_CustomerContact.get("Account_Name").get("id").toLong()
  91.      } 
  92.  } 
  93.  // 
  94.  // get organization phone number 
  95.  m_OrgDetails = Map()
  96.  r_Response = zoho.crm.invokeConnector("crm.getorg",m_OrgDetails)
  97.  if(!isnull(r_Response.get("status_code"))) 
  98.  { 
  99.      if(r_Response.get("status_code") == 200) 
  100.      { 
  101.          l_OrgDetails = r_Response.get("response").get("org")
  102.          for each  r_Org in l_OrgDetails 
  103.          { 
  104.              m_Data.put("org.phone",r_Org.get("phone"))
  105.          } 
  106.      } 
  107.  } 
  108.  // 
  109.  // map other values from this record 
  110.  v_OwnerID = "0"
  111.  if(!isNull(r_RecordDetails.get("Attending_Engineer"))) 
  112.  { 
  113.      m_Data.put("Attending_Engineer",r_RecordDetails.get("Attending_Engineer").get("name"))
  114.  } 
  115.  if(!isNull(r_RecordDetails.get("Service_Start_Time"))) 
  116.  { 
  117.      m_Data.put("Service_Start_Time",r_RecordDetails.get("Service_Start_Time").replaceAll("T"," ",true).getPrefix("+").toTime().toString("dd-MMM-yyyy HH:mm","Europe/London"))
  118.  } 
  119.  if(!isNull(r_RecordDetails.get("Service_End_Time"))) 
  120.  { 
  121.      m_Data.put("Service_End_Time",r_RecordDetails.get("Service_End_Time").replaceAll("T"," ",true).getPrefix("+").toTime().toString("dd-MMM-yyyy HH:mm","Europe/London"))
  122.  } 
  123.  // ... MAP FURTHER VALUES 
  124.  // 
  125.  // merge the data into the template (for ZohoWriter) 
  126.  m_MergedData = Map()
  127.  m_MergedData.put("merge_data",{"data":m_Data})
  128.  //info m_MergedData; 
  129.  // 
  130.  // *************************************** 
  131.  // default first recipient to record owner 
  132.  l_Recipients = List()
  133.  v_EngineerID = r_RecordDetails.get("Owner").get("id")
  134.  v_EngineerFullName = r_RecordDetails.get("Owner").get("name")
  135.  v_EngineerEmail = r_RecordDetails.get("Owner").get("email")
  136.  // 
  137.  // get first recipient (engineer) 
  138.  if(!isNull(r_RecordDetails.get("Attending_Engineer"))) 
  139.  { 
  140.      v_User_EngineerID = r_RecordDetails.get("Attending_Engineer").get("id")
  141.      r_Users = zoho.crm.getRecords("users")
  142.      for each  m_User in r_Users.get("users") 
  143.      { 
  144.          if(m_User.get("id").equalsIgnoreCase(v_User_EngineerID)) 
  145.          { 
  146.              v_EngineerID = m_User.get("id")
  147.              v_EngineerFullName = m_User.get("full_name")
  148.              v_EngineerEmail = m_User.get("email")
  149.          } 
  150.          // 
  151.          // get manager approver while we're here (where manager name = Joel Lipman) 
  152.          if(m_User.get("full_name").equalsIgnoreCase("Joel Lipman")) 
  153.          { 
  154.              v_ManagerID = m_User.get("id")
  155.              v_ManagerFullName = m_User.get("full_name")
  156.              v_ManagerEmail = m_User.get("email")
  157.          } 
  158.      } 
  159.  } 
  160.  // 
  161.  // OVERRIDES: for testing purposes 
  162.  if(b_Debug) 
  163.  { 
  164.      v_EngineerFullName = "Engineering Joel"
  165.      v_EngineerEmail = "me+This email address is being protected from spambots. You need JavaScript enabled to view it."
  166.      v_ManagerFullName = "Approving Joel"
  167.      v_ManagerEmail = "me+This email address is being protected from spambots. You need JavaScript enabled to view it."
  168.      v_EndCustomerFullName = "Customer Joel"
  169.      v_EndCustomerEmail = "me+This email address is being protected from spambots. You need JavaScript enabled to view it."
  170.  } 
  171.  // 
  172.  // engineer recipient (job notes to add - A4 size) 
  173.  m_RecipientSigner = Map()
  174.  m_RecipientSigner.put("recipient_1",v_EngineerEmail)
  175.  m_RecipientSigner.put("recipient_name",v_EngineerFullName)
  176.  m_RecipientSigner.put("action_type","sign")
  177.  m_RecipientSigner.put("private_notes","Please complete and sign the document before it is sent for manager approval: " + v_ManagerFullName)
  178.  l_Recipients.add(m_RecipientSigner)
  179.  // 
  180.  // manager recipient (approver) 
  181.  m_RecipientCopy = Map()
  182.  m_RecipientCopy.put("recipient_2",v_ManagerEmail)
  183.  m_RecipientCopy.put("recipient_name",v_ManagerFullName)
  184.  m_RecipientCopy.put("action_type","approve")
  185.  m_RecipientCopy.put("private_notes","The engineer has completed this document.  Please check and approve this document before I send this to the customer: " + v_EndCustomerFullName)
  186.  l_Recipients.add(m_RecipientCopy)
  187.  // 
  188.  // end user recipient (approver) 
  189.  m_RecipientCopy = Map()
  190.  m_RecipientCopy.put("recipient_3",v_EndCustomerEmail)
  191.  m_RecipientCopy.put("recipient_name",v_EndCustomerFullName)
  192.  m_RecipientCopy.put("action_type","approve")
  193.  m_RecipientCopy.put("private_notes","Please read and confirm you approve of this document.  You will receive a copy for your records once completed.")
  194.  l_Recipients.add(m_RecipientCopy)
  195.  //info l_Recipients; 
  196.  // 
  197.  // some signing options 
  198.  v_DaysUntilExpiry = 3
  199.  m_Options = Map()
  200.  m_Options.put("sign_in_order","true")
  201.  m_Options.put("set_expire",v_DaysUntilExpiry.toString())
  202.  m_Options.put("reminder_period",v_DaysUntilExpiry)
  203.  // 
  204.  // send for signature 
  205.  v_Filename = v_FileNameWithouExt + ".pdf"
  206.  r_ZohoSignRequest = zoho.writer.mergeAndSign(v_TemplateID,m_MergedData,v_Filename,l_Recipients,m_Options,"my_writer_connection")
  207.  // info r_ZohoSignRequest; 
  208.  // 
  209.  // track in CRM Zoho Sign (need request ID) 
  210.  l_ZohoSignResponses = ifnull(r_ZohoSignRequest.get("records"),List())
  211.  for each  m_ZohoSignResponse in l_ZohoSignResponses 
  212.  { 
  213.      if(!isNull(m_ZohoSignResponse.get("sign_request_id"))) 
  214.      { 
  215.          // 
  216.          // Create CRM Record ZohoSign Documents for tracking progress 
  217.          m_ZCrm_ZSDoc = Map()
  218.          m_ZCrm_ZSDoc.put("Name",v_FileNameWithouExt)
  219.          m_ZCrm_ZSDoc.put("zohosign__Contact",v_EndCustomerContactID)
  220.          m_ZCrm_ZSDoc.put("Email",v_EndCustomerEmail)
  221.          if(v_EndCustomerAccountID != 0) 
  222.          { 
  223.              m_ZCrm_ZSDoc.put("zohosign__Account",v_EndCustomerAccountID)
  224.          } 
  225.          m_ZCrm_ZSDoc.put("zohosign__Owner",v_ZCRM_DocOwnerID)
  226.          m_ZCrm_ZSDoc.put("zohosign__Date_Sent",zoho.currentdate)
  227.          m_ZCrm_ZSDoc.put("zohosign__Module_Name",v_ModuleName)
  228.          m_ZCrm_ZSDoc.put("zohosign__Module_Record_ID",p_MaintenanceID.toString())
  229.          m_ZCrm_ZSDoc.put("zohosign__Document_Deadline",zoho.currentdate.addDay(v_DaysUntilExpiry))
  230.          m_ZCrm_ZSDoc.put("zohosign__Document_Status","Out for Signature")
  231.          // Request_ID is a custom field added to retrieve the document later from ZohoWriter. 
  232.          m_ZCrm_ZSDoc.put("Request_ID",m_ZohoSignResponse.get("sign_request_id"))
  233.          r_ZCrm_ZSDoc = zoho.crm.createRecord("zohosign__ZohoSign_Documents",m_ZCrm_ZSDoc)
  234.          // 
  235.          // create CRM zohosign document child records (recipient and event) 
  236.          if(!isNull(r_ZCrm_ZSDoc.get("id"))) 
  237.          { 
  238.              // 
  239.              // get logged in user (who clicked the button) 
  240.              v_LoggedInUserID = 0
  241.              r_Users = zoho.crm.getRecords("users")
  242.              for each  m_User in r_Users.get("users") 
  243.              { 
  244.                  if(m_User.get("email").equalsIgnoreCase(zoho.loginuserid)) 
  245.                  { 
  246.                      v_LoggedInUserID = m_User.get("id")
  247.                  } 
  248.              } 
  249.              // 
  250.              // create CRM zohosign document recipient(s) 
  251.              v_RecipientIndex = 0
  252.              for each  m_Recipient in l_Recipients 
  253.              { 
  254.                  v_RecipientIndex = v_RecipientIndex + 1
  255.                  m_CreateRecipient = Map()
  256.                  m_CreateRecipient.put("Name",m_Recipient.get("recipient_name"))
  257.                  m_CreateRecipient.put("Email",m_Recipient.get("recipient_" + v_RecipientIndex))
  258.                  m_CreateRecipient.put("Owner",v_LoggedInUserID)
  259.                  m_CreateRecipient.put("zohosign__Recipient_Order",v_RecipientIndex)
  260.                  m_CreateRecipient.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"))
  261.                  v_RecipientType = if(m_Recipient.get("action_type") == "sign","Signer","Approver")
  262.                  m_CreateRecipient.put("zohosign__Recipient_Type",v_RecipientType)
  263.                  m_CreateRecipient.put("zohosign__Recipient_Status","Sent")
  264.                  r_CreateRecipient = zoho.crm.createRecord("zohosign__ZohoSign_Recipients",m_CreateRecipient)
  265.              } 
  266.              // 
  267.              // create CRM zohosign document event (1st to engineer) 
  268.              m_CreateEvent = Map()
  269.              m_CreateEvent.put("Name",v_SignType + "-SENT")
  270.              m_CreateEvent.put("Owner",v_LoggedInUserID)
  271.              m_CreateEvent.put("Email",zoho.loginuserid)
  272.              m_CreateEvent.put("zohosign__Date",zoho.currentdate)
  273.              m_CreateEvent.put("zohosign__ZohoSign_Document",r_ZCrm_ZSDoc.get("id"))
  274.              m_CreateEvent.put("zohosign__Description","Sent out for signature")
  275.              r_CreateEvent = zoho.crm.createRecord("zohosign__ZohoSign_Document_Events",m_CreateEvent)
  276.          } 
  277.          // 
  278.          // output result to user 
  279.          v_OutputMessage = v_SignType + " sent to " + v_EngineerFullName + (" + v_EngineerEmail + ") for signing."
  280.      } 
  281.  } 
  282.  // 
  283.  // return response to user 
  284.  return v_OutputMessage; 

Caveat

Genericizing the webhook
If your subscription plan doesn't allow more than 2 webhooks, then you can't use a different webhook for each event. Instead I've created the one webhook to rule them all which triggers on any event:
copyraw
/* *******************************************************************************
Function:       String fn_ZohoSign_Webhook(string crmAPIRequest)
Label:          Fn - ZohoSign - Document Approved
Trigger:        Webhook when ZohoSign sees an approved document
Purpose:	    This function is used as a webhook when ZohoSign detects an event
Inputs:         crmAPIRequest
Outputs:        crmAPIResponse

Date Created:   2024-08-06 (Joel Lipman)
                - Initial release
Date Modified:	2024-08-07 (Joel Lipman)
                - Modified to attach the signed document to the originating Contact Record
Date Modified:	2024-08-21 (Joel Lipman)
                - Modified to allow for any event from ZohoSign (as Subscription Plan only allowed 2 webhooks)
Date Modified:	2024-08-22 (Joel Lipman)
                - Handles forwarded and declined events

More Information:
                https://www.zoho.com/deluge/help/sign-tasks.html
				https://www.zoho.com/deluge/help/sign/get-document-by-id.html
				https://www.zoho.com/deluge/help/sign/download-document.html

				Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all
******************************************************************************* */
//
// initialize
b_DebugMode = true;
v_DebugMessage = "";
v_ZS_RequestName = "";
v_ZS_RequestID = 0;
v_ActionType = "Viewed";
b_UpdateRecipient = false;
v_DocStatusSuffix = "UNKNOWN";
v_ZCRM_DocCreated = null;
v_ZCRM_DocRecordID = 0;
v_ZCRM_DocRecordOwnerID = 0;
v_ZCRM_DocRecordAccountID = 0;
v_RecipientEmail = "";
//
// parse webhook body
m_Body = ifnull(crmAPIRequest.get("body"),Map());
if(b_DebugMode)
{
	v_DebugMessage = "Body:<br /><br />" + m_Body + "<hr />";
}
//
// parse webhook requests
if(!isNull(m_Body.get("requests")))
{
	m_Request = m_Body.get("requests");
	v_ZS_RequestID = ifnull(m_Request.get("request_id"),"0").toLong();
	v_ZS_RequestName = ifnull(m_Request.get("request_name"),"");
	v_ZCRM_DocRecordName = v_ZS_RequestName;
	if(b_DebugMode)
	{
		v_DebugMessage = v_DebugMessage + "Request: <br /><br />" + v_ZS_RequestID + " :: " + v_ZS_RequestName + "<hr />";
	}
	//
	// get the ZohoSign Document record in CRM
	if(v_ZS_RequestID != 0)
	{
		//
		// find the CRM "ZohoSign Documents" record
		l_SearchResults = zoho.crm.searchRecords("zohosign__ZohoSign_Documents","Request_ID:equals:" + v_ZS_RequestID);
		for each  m_ZCRM_Doc in l_SearchResults
		{
			//
			// check we have the right one
			if(m_ZCRM_Doc.get("Request_ID") == v_ZS_RequestID)
			{
				//
				// retrieve the CRM "ZohoSign Documents" record ID and owner
				v_ZCRM_DocRecordName = m_ZCRM_Doc.get("Name");
				v_ZCRM_DocRecordID = m_ZCRM_Doc.get("id").toLong();
				v_ZCRM_DocRecordOwnerID = m_ZCRM_Doc.get("Owner").get("id").toLong();
				v_ZCRM_DocRecordAccountID = m_ZCRM_Doc.get("zohosign__Account").get("id").toLong();
				v_RecipientEmail = m_ZCRM_Doc.get("Email");
				v_ZCRM_DocCreated = m_ZCRM_Doc.get("Created_Time");
			}
		}
		if(b_DebugMode)
		{
			v_DebugMessage = v_DebugMessage + "Document: <br /><br />" + v_ZCRM_DocRecordID + " :: " + v_ZCRM_DocRecordOwnerID + "<br />If this is zero then no matching record was found.<hr />";
		}
		//
		// get ZohoSign Document ID (only if template exists in ZohoSign)
		v_ZS_DocID = 0;
		for each  m_Document in m_Request.get("document_ids")
		{
			if(!isNull(m_Document.get("document_id")))
			{
				v_ZS_DocID = m_Document.get("document_id").toLong();
			}
		}
		if(b_DebugMode)
		{
			v_DebugMessage = v_DebugMessage + "ZS Document: <br /><br />" + v_ZS_DocID + "<hr />";
		}
		//
		//
		// ****************************************
		// capture different events
		if(v_ZCRM_DocRecordID != 0)
		{
			//
			// only create document events for the following
			l_CreateEventsFor = {"RequestViewed","RequestSigningSuccess","RequestApproved","RequestForwarded","RequestCompleted","RequestRejected"};
			//
			// check notifications
			if(!isNull(m_Body.get("notifications")))
			{
				m_UpdateDoc = Map();
				m_Notification = m_Body.get("notifications");
				//
				// evaluate values for CRM zohosign document event
				if(l_CreateEventsFor.contains(m_Notification.get("operation_type")))
				{
					if(m_Notification.get("operation_type") == "RequestViewed")
					{
						v_DocStatusSuffix = "VIEWED";
						b_UpdateRecipient = true;
						v_RecipientEmail = m_Notification.get("performed_by_email");
					}
					else if(m_Notification.get("operation_type") == "RequestApproved")
					{
						v_DocStatusSuffix = "APPROVED";
						b_UpdateRecipient = true;
						v_RecipientEmail = m_Notification.get("performed_by_email");
					}
					else if(m_Notification.get("operation_type") == "RequestSigningSuccess")
					{
						v_DocStatusSuffix = "SIGNED";
						b_UpdateRecipient = true;
						v_RecipientEmail = m_Notification.get("performed_by_email");
					}
					else if(m_Notification.get("operation_type") == "RequestCompleted")
					{
						v_DocStatusSuffix = "COMPLETED";
						b_UpdateRecipient = true;
						//
						// time to complete
						if(!isNull(v_ZCRM_DocCreated))
						{
							v_DaysBetween = v_ZCRM_DocCreated.getPrefix("T").toDate().daysBetween(zoho.currentdate);
							v_DaysBetweenStr = v_DaysBetween + if(v_DaysBetween == 1," day"," days");
							m_UpdateDoc.put("zohosign__Time_to_complete",v_DaysBetweenStr);
						}
						//
						m_UpdateDoc.put("zohosign__Date_Completed",zoho.currentdate);
					}
					else if(m_Notification.get("operation_type") == "RequestForwarded")
					{
						v_DocStatusSuffix = "FORWARDED";
						//
						// create a new/use existing CRM contact
						v_MatchedContactID = 0;
						v_RecipientEmail = m_Notification.get("beneficiary_email");
						l_SearchContacts = zoho.crm.searchRecords("Contacts","Email:equals:" + m_Notification.get("beneficiary_email"));
						for each  m_MatchedContact in l_SearchContacts
						{
							if(!isNull(m_MatchedContact.get("id")))
							{
								v_MatchedContactID = m_MatchedContact.get("id").toLong();
							}
						}
						if(v_MatchedContactID == 0)
						{
							v_FullName = ifnull(m_Notification.get("beneficiary_name"),"").trim();
							m_CreateContact = Map();
							if(v_FullName.contains(" "))
							{
								v_Forenames = v_FullName.subString(0,v_FullName.lastIndexOf(" "));
								v_LastName = v_FullName.subString(v_FullName.lastIndexOf(" "));
								m_CreateContact.put("First_Name",v_Forenames);
								m_CreateContact.put("Last_Name",v_LastName);
							}
							else
							{
								m_CreateContact.put("First_Name",v_FullName);
							}
							m_CreateContact.put("Email",m_Notification.get("beneficiary_email"));
							m_CreateContact.put("Account_Name",v_ZCRM_DocRecordAccountID);
							m_CreateContact.put("Contact_Status","Active");
							m_CreateContact.put("Lead_Source_Detail","Created as a forwarded signee in ZohoSign");
							r_CreateContact = zoho.crm.createRecord("Contacts",m_CreateContact);
							if(!isNull(r_CreateContact.get("id")))
							{
								v_MatchedContactID = r_CreateContact.get("id").toLong();
							}
						}
						//
						m_UpdateDoc.put("Email",m_Notification.get("beneficiary_email"));
						m_UpdateDoc.put("zohosign__Contact",v_MatchedContactID);
					}
					else if(m_Notification.get("operation_type") == "RequestRejected")
					{
						v_DocStatusSuffix = "DECLINED";
						b_UpdateRecipient = true;
						v_RecipientEmail = m_Notification.get("performed_by_email");
						//
						m_UpdateDoc.put("zohosign__Date_Declined",zoho.currentdate);
						m_UpdateDoc.put("zohosign__Declined_Reason",m_Notification.get("reason"));
					}
					//
					// finish updating the ZohoSign Document record
					m_UpdateDoc.put("zohosign__Document_Status",v_DocStatusSuffix.proper());
					r_UpdateDoc = zoho.crm.updateRecord("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID,m_UpdateDoc);
					//
					// create CRM zohosign document event
					m_CreateEvent = Map();
					m_CreateEvent.put("Name",v_ZCRM_DocRecordName + " -" + v_DocStatusSuffix);
					m_CreateEvent.put("Owner",v_ZCRM_DocRecordOwnerID);
					if(m_Notification.get("performed_by_email") != "System Generated")
					{
						m_CreateEvent.put("Email",m_Notification.get("performed_by_email"));
					}
					m_CreateEvent.put("zohosign__Date",zoho.currentdate);
					m_CreateEvent.put("zohosign__ZohoSign_Document",v_ZCRM_DocRecordID);
					m_CreateEvent.put("zohosign__Description",m_Notification.get("activity"));
					//
					// create the event record
					r_CreateEvent = zoho.crm.createRecord("zohosign__ZohoSign_Document_Events",m_CreateEvent);
					if(b_DebugMode)
					{
						v_DebugMessage = v_DebugMessage + "ZCRM Event Record: <br /><br />" + r_CreateEvent + "<hr />";
					}
					//
					// Fix for ZohoSign: if forwarded to another signee then completed, webhook on completed is not triggered???
					if(m_Request.get("request_status") == "completed")
					{
						m_UpdateDoc = Map();
						//
						// time to complete
						if(!isNull(v_ZCRM_DocCreated))
						{
							v_DaysBetween = v_ZCRM_DocCreated.getPrefix("T").toDate().daysBetween(zoho.currentdate);
							v_DaysBetweenStr = if(v_DaysBetween == 1," day","days");
							m_UpdateDoc.put("zohosign__Time_to_complete",v_DaysBetweenStr);
						}
						r_DocDetails = zoho.crm.getRecordById("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID);
						v_ExistingNotes = ifnull(r_DocDetails.get("zohosign__Document_Note"),"");
						l_ExistingNotes = v_ExistingNotes.toList("\n");
						l_ExistingNotes.add("Completed: " + zoho.currenttime.toString("yyyy-MM-dd HH:mm:ss","Europe/London"));
						m_UpdateDoc.put("zohosign__Document_Status","Completed");
						m_UpdateDoc.put("zohosign__Document_Note",l_ExistingNotes.toString("\n"));
						m_UpdateDoc.put("zohosign__Date_Completed",zoho.currentdate);
						r_UpdateDoc = zoho.crm.updateRecord("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID,m_UpdateDoc);
					}
					//
					// if we want to update the recipient record
					if(b_UpdateRecipient)
					{
						//
						// find the relevant recipient record
						for each  m_Action in m_Request.get("actions")
						{
							if(!isNull(m_Action.get("action_status")) && v_RecipientEmail != "")
							{
								v_ZCRM_RecipientRecordID = 0;
								l_SearchRecipients = zoho.crm.searchRecords("zohosign__ZohoSign_Recipients","(Email:equals:" + v_RecipientEmail + ")and(zohosign__ZohoSign_Document:equals:" + v_ZCRM_DocRecordID + ")");
								for each  m_Recipient in l_SearchRecipients
								{
									if(!isNull(m_Recipient.get("id")))
									{
										v_ZCRM_RecipientRecordID = m_Recipient.get("id").toLong();
									}
								}
								if(v_ZCRM_RecipientRecordID == 0 && !m_Action.get("action_status").equalsIgnoreCase("FORWARDED"))
								{
									//
									// create CRM zohosign document recipient record
									m_CreateRecipient = Map();
									m_CreateRecipient.put("Name",m_Action.get("recipient_name"));
									m_CreateRecipient.put("Email",m_Action.get("recipient_email"));
									m_CreateRecipient.put("Owner",v_ZCRM_DocRecordOwnerID);
									m_CreateRecipient.put("zohosign__Recipient_Order",m_Action.get("signing_order"));
									if(v_DocStatusSuffix == "COMPLETED")
									{
										m_CreateRecipient.put("zohosign__Date_Completed",zoho.currentdate);
									}
									else if(v_DocStatusSuffix == "DECLINED")
									{
										m_CreateRecipient.put("zohosign__Date_Declined",zoho.currentdate);
									}
									else
									{
										m_CreateRecipient.put("zohosign__Date_Delivered",zoho.currentdate);
									}
									m_CreateRecipient.put("zohosign__ZohoSign_Document",v_ZCRM_DocRecordID);
									v_RecipientType = "CC";
									if(m_Action.get("action_type").equalsIgnoreCase("APPROVE"))
									{
										v_RecipientType = "Approver";
									}
									else if(m_Action.get("action_type").equalsIgnoreCase("SIGN"))
									{
										v_RecipientType = "Signer";
									}
									m_CreateRecipient.put("zohosign__Recipient_Type",v_RecipientType);
									v_RecipientStatus = if(v_DocStatusSuffix == "SIGNED" || v_DocStatusSuffix == "APPROVED" || v_DocStatusSuffix == "FORWARDED" || v_DocStatusSuffix == "VIEWED","Delivered",v_DocStatusSuffix.proper());
									m_CreateRecipient.put("zohosign__Recipient_Status",v_RecipientStatus);
									if(v_ZS_DocID != 0)
									{
										m_CreateRecipient.put("zohosign__ZohoSign_Document_ID",v_ZS_DocID.toString());
									}
									//
									// create the recipient record
									r_CreateRecipient = zoho.crm.createRecord("zohosign__ZohoSign_Recipients",m_CreateRecipient);
									if(b_DebugMode)
									{
										v_DebugMessage = v_DebugMessage + "ZCRM Recipient Record - CREATE: <br />" + m_Action.get("recipient_name") + "<br />" + r_CreateRecipient + "<hr />";
									}
								}
								else if(v_ZCRM_RecipientRecordID != 0 && m_Action.get("recipient_email").equalsIgnoreCase(m_Notification.get("performed_by_email")))
								{
									//
									// update CRM zohosign document recipient record
									m_UpdateRecipient = Map();
									m_UpdateRecipient.put("zohosign__Recipient_Order",m_Action.get("signing_order"));
									v_RecipientType = "CC";
									if(m_Action.get("action_type").equalsIgnoreCase("APPROVE"))
									{
										v_RecipientType = "Approver";
									}
									else if(m_Action.get("action_type").equalsIgnoreCase("SIGN"))
									{
										v_RecipientType = "Signer";
									}
									m_UpdateRecipient.put("zohosign__Recipient_Type",v_RecipientType);
									v_RecipientStatus = if(v_DocStatusSuffix == "SIGNED" || v_DocStatusSuffix == "APPROVED" || v_DocStatusSuffix == "FORWARDED" || v_DocStatusSuffix == "VIEWED","Delivered",v_DocStatusSuffix.proper());
									m_UpdateRecipient.put("zohosign__Recipient_Status",v_RecipientStatus);
									if(v_DocStatusSuffix == "COMPLETED")
									{
										m_UpdateRecipient.put("zohosign__Date_Completed",zoho.currentdate);
									}
									else if(v_DocStatusSuffix == "DECLINED")
									{
										m_UpdateRecipient.put("zohosign__Date_Declined",zoho.currentdate);
									}
									else
									{
										m_UpdateRecipient.put("zohosign__Date_Delivered",zoho.currentdate);
									}
									if(v_ZS_DocID != 0)
									{
										m_UpdateRecipient.put("zohosign__ZohoSign_Document_ID",v_ZS_DocID.toString());
									}
									r_UpdateRecipient = zoho.crm.updateRecord("zohosign__ZohoSign_Recipients",v_ZCRM_RecipientRecordID,m_UpdateRecipient);
									if(b_DebugMode)
									{
										v_DebugMessage = v_DebugMessage + "ZCRM Recipient Record - UPDATE: <br /><br />" + r_UpdateRecipient + "<hr />";
									}
								}
							}
						}
					}
				}
			}
		}
		//
		// now attach to CRM Record if completed
		if(v_ZCRM_DocRecordID != 0 && v_DocStatusSuffix == "COMPLETED")
		{
			r_ZCRM_DocumentDetails = zoho.crm.getRecordById("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID);
			if(!isNull(r_ZCRM_DocumentDetails.get("zohosign__Module_Record_ID")))
			{
				//
				// get module and record ID
				v_ModuleName = ifnull(r_ZCRM_DocumentDetails.get("zohosign__Module_Name"),"-");
				v_ModuleRecordID = r_ZCRM_DocumentDetails.get("zohosign__Module_Record_ID");
				v_ZS_DocumentName = ifnull(r_ZCRM_DocumentDetails.get("Name"),v_ModuleName);
				//
				// download file
				r_ZS_Download = zoho.sign.downloadDocument(v_ZS_RequestID);
				v_Filename = v_ZS_DocumentName;
				if(!isNull(v_ZS_RequestName))
				{
					v_Filename = v_ZS_RequestName;
				}
				if(v_Filename.lastIndexOf(".") > 0)
				{
					v_Filename = v_Filename.subString(0,v_Filename.lastIndexOf("."));
				}
				r_ZS_Download.setFileName(v_Filename + "_SIGNED.pdf");
				//
				// attach file to record of module
				r_AttachFile = zoho.crm.attachFile(v_ModuleName,v_ModuleRecordID,r_ZS_Download);
				//
				// for debug purposes but completely unnecessary for the function of this function
				if(!isNull(r_AttachFile.get("message")))
				{
					v_DebugMessage = v_DebugMessage + "CRM Attachment: <br /><br />" + r_AttachFile.get("message") + "<hr />";
				}
			}
		}
	}
}
v_DebugMessage = v_DebugMessage + "Date/Time: <br /><br />" + zoho.currenttime;
//
if(b_DebugMode)
{
	sendmail
	[
		from :zoho.adminuserid
		to :"This email address is being protected from spambots. You need JavaScript enabled to view it."
		subject :"My ZohoSign Webhook - ANY EVENT"
		message :v_DebugMessage
	]
}
return "";
  1.  /* ******************************************************************************* 
  2.  Function:       string fn_ZohoSign_Webhook(string crmAPIRequest) 
  3.  Label:          Fn - ZohoSign - Document Approved 
  4.  Trigger:        Webhook when ZohoSign sees an approved document 
  5.  Purpose:        This function is used as a webhook when ZohoSign detects an event 
  6.  Inputs:         crmAPIRequest 
  7.  Outputs:        crmAPIResponse 
  8.   
  9.  Date Created:   2024-08-06 (Joel Lipman) 
  10.                  - Initial release 
  11.  Date Modified:    2024-08-07 (Joel Lipman) 
  12.                  - Modified to attach the signed document to the originating Contact Record 
  13.  Date Modified:    2024-08-21 (Joel Lipman) 
  14.                  - Modified to allow for any event from ZohoSign (as Subscription Plan only allowed 2 webhooks) 
  15.  Date Modified:    2024-08-22 (Joel Lipman) 
  16.                  - Handles forwarded and declined events 
  17.   
  18.  More Information: 
  19.                  https://www.zoho.com/deluge/help/sign-tasks.html 
  20.                  https://www.zoho.com/deluge/help/sign/get-document-by-id.html 
  21.                  https://www.zoho.com/deluge/help/sign/download-document.html 
  22.   
  23.                  Scope(s): ZohoSign.documents.ALL, ZohoSign.account.ALL, ZohoSign.templates.ALL, ZohoWriter.documentEditor.ALL, ZohoWriter.Merge.ALL, ZohoSign.setup.READ, WorkDrive.workspace.ALL, WorkDrive.files.ALL, ZohoCRM.modules.attachments.all 
  24.  ******************************************************************************* */ 
  25.  // 
  26.  // initialize 
  27.  b_DebugMode = true
  28.  v_DebugMessage = ""
  29.  v_ZS_RequestName = ""
  30.  v_ZS_RequestID = 0
  31.  v_ActionType = "Viewed"
  32.  b_UpdateRecipient = false
  33.  v_DocStatusSuffix = "UNKNOWN"
  34.  v_ZCRM_DocCreated = null
  35.  v_ZCRM_DocRecordID = 0
  36.  v_ZCRM_DocRecordOwnerID = 0
  37.  v_ZCRM_DocRecordAccountID = 0
  38.  v_RecipientEmail = ""
  39.  // 
  40.  // parse webhook body 
  41.  m_Body = ifnull(crmAPIRequest.get("body"),Map())
  42.  if(b_DebugMode) 
  43.  { 
  44.      v_DebugMessage = "Body:<br /><br />" + m_Body + "<hr />"
  45.  } 
  46.  // 
  47.  // parse webhook requests 
  48.  if(!isNull(m_Body.get("requests"))) 
  49.  { 
  50.      m_Request = m_Body.get("requests")
  51.      v_ZS_RequestID = ifnull(m_Request.get("request_id"),"0").toLong()
  52.      v_ZS_RequestName = ifnull(m_Request.get("request_name"),"")
  53.      v_ZCRM_DocRecordName = v_ZS_RequestName; 
  54.      if(b_DebugMode) 
  55.      { 
  56.          v_DebugMessage = v_DebugMessage + "Request: <br /><br />" + v_ZS_RequestID + :: " + v_ZS_RequestName + "<hr />"
  57.      } 
  58.      // 
  59.      // get the ZohoSign Document record in CRM 
  60.      if(v_ZS_RequestID != 0) 
  61.      { 
  62.          // 
  63.          // find the CRM "ZohoSign Documents" record 
  64.          l_SearchResults = zoho.crm.searchRecords("zohosign__ZohoSign_Documents","Request_ID:equals:" + v_ZS_RequestID)
  65.          for each  m_ZCRM_Doc in l_SearchResults 
  66.          { 
  67.              // 
  68.              // check we have the right one 
  69.              if(m_ZCRM_Doc.get("Request_ID") == v_ZS_RequestID) 
  70.              { 
  71.                  // 
  72.                  // retrieve the CRM "ZohoSign Documents" record ID and owner 
  73.                  v_ZCRM_DocRecordName = m_ZCRM_Doc.get("Name")
  74.                  v_ZCRM_DocRecordID = m_ZCRM_Doc.get("id").toLong()
  75.                  v_ZCRM_DocRecordOwnerID = m_ZCRM_Doc.get("Owner").get("id").toLong()
  76.                  v_ZCRM_DocRecordAccountID = m_ZCRM_Doc.get("zohosign__Account").get("id").toLong()
  77.                  v_RecipientEmail = m_ZCRM_Doc.get("Email")
  78.                  v_ZCRM_DocCreated = m_ZCRM_Doc.get("Created_Time")
  79.              } 
  80.          } 
  81.          if(b_DebugMode) 
  82.          { 
  83.              v_DebugMessage = v_DebugMessage + "Document: <br /><br />" + v_ZCRM_DocRecordID + :: " + v_ZCRM_DocRecordOwnerID + "<br />If this is zero then no matching record was found.<hr />"
  84.          } 
  85.          // 
  86.          // get ZohoSign Document ID (only if template exists in ZohoSign) 
  87.          v_ZS_DocID = 0
  88.          for each  m_Document in m_Request.get("document_ids") 
  89.          { 
  90.              if(!isNull(m_Document.get("document_id"))) 
  91.              { 
  92.                  v_ZS_DocID = m_Document.get("document_id").toLong()
  93.              } 
  94.          } 
  95.          if(b_DebugMode) 
  96.          { 
  97.              v_DebugMessage = v_DebugMessage + "ZS Document: <br /><br />" + v_ZS_DocID + "<hr />"
  98.          } 
  99.          // 
  100.          // 
  101.          // **************************************** 
  102.          // capture different events 
  103.          if(v_ZCRM_DocRecordID != 0) 
  104.          { 
  105.              // 
  106.              // only create document events for the following 
  107.              l_CreateEventsFor = {"RequestViewed","RequestSigningSuccess","RequestApproved","RequestForwarded","RequestCompleted","RequestRejected"}
  108.              // 
  109.              // check notifications 
  110.              if(!isNull(m_Body.get("notifications"))) 
  111.              { 
  112.                  m_UpdateDoc = Map()
  113.                  m_Notification = m_Body.get("notifications")
  114.                  // 
  115.                  // evaluate values for CRM zohosign document event 
  116.                  if(l_CreateEventsFor.contains(m_Notification.get("operation_type"))) 
  117.                  { 
  118.                      if(m_Notification.get("operation_type") == "RequestViewed") 
  119.                      { 
  120.                          v_DocStatusSuffix = "VIEWED"
  121.                          b_UpdateRecipient = true
  122.                          v_RecipientEmail = m_Notification.get("performed_by_email")
  123.                      } 
  124.                      else if(m_Notification.get("operation_type") == "RequestApproved") 
  125.                      { 
  126.                          v_DocStatusSuffix = "APPROVED"
  127.                          b_UpdateRecipient = true
  128.                          v_RecipientEmail = m_Notification.get("performed_by_email")
  129.                      } 
  130.                      else if(m_Notification.get("operation_type") == "RequestSigningSuccess") 
  131.                      { 
  132.                          v_DocStatusSuffix = "SIGNED"
  133.                          b_UpdateRecipient = true
  134.                          v_RecipientEmail = m_Notification.get("performed_by_email")
  135.                      } 
  136.                      else if(m_Notification.get("operation_type") == "RequestCompleted") 
  137.                      { 
  138.                          v_DocStatusSuffix = "COMPLETED"
  139.                          b_UpdateRecipient = true
  140.                          // 
  141.                          // time to complete 
  142.                          if(!isNull(v_ZCRM_DocCreated)) 
  143.                          { 
  144.                              v_DaysBetween = v_ZCRM_DocCreated.getPrefix("T").toDate().daysBetween(zoho.currentdate)
  145.                              v_DaysBetweenStr = v_DaysBetween + if(v_DaysBetween == 1," day"," days")
  146.                              m_UpdateDoc.put("zohosign__Time_to_complete",v_DaysBetweenStr)
  147.                          } 
  148.                          // 
  149.                          m_UpdateDoc.put("zohosign__Date_Completed",zoho.currentdate)
  150.                      } 
  151.                      else if(m_Notification.get("operation_type") == "RequestForwarded") 
  152.                      { 
  153.                          v_DocStatusSuffix = "FORWARDED"
  154.                          // 
  155.                          // create a new/use existing CRM contact 
  156.                          v_MatchedContactID = 0
  157.                          v_RecipientEmail = m_Notification.get("beneficiary_email")
  158.                          l_SearchContacts = zoho.crm.searchRecords("Contacts","Email:equals:" + m_Notification.get("beneficiary_email"))
  159.                          for each  m_MatchedContact in l_SearchContacts 
  160.                          { 
  161.                              if(!isNull(m_MatchedContact.get("id"))) 
  162.                              { 
  163.                                  v_MatchedContactID = m_MatchedContact.get("id").toLong()
  164.                              } 
  165.                          } 
  166.                          if(v_MatchedContactID == 0) 
  167.                          { 
  168.                              v_FullName = ifnull(m_Notification.get("beneficiary_name"),"").trim()
  169.                              m_CreateContact = Map()
  170.                              if(v_FullName.contains(" ")) 
  171.                              { 
  172.                                  v_Forenames = v_FullName.subString(0,v_FullName.lastIndexOf(" "))
  173.                                  v_LastName = v_FullName.subString(v_FullName.lastIndexOf(" "))
  174.                                  m_CreateContact.put("First_Name",v_Forenames)
  175.                                  m_CreateContact.put("Last_Name",v_LastName)
  176.                              } 
  177.                              else 
  178.                              { 
  179.                                  m_CreateContact.put("First_Name",v_FullName)
  180.                              } 
  181.                              m_CreateContact.put("Email",m_Notification.get("beneficiary_email"))
  182.                              m_CreateContact.put("Account_Name",v_ZCRM_DocRecordAccountID)
  183.                              m_CreateContact.put("Contact_Status","Active")
  184.                              m_CreateContact.put("Lead_Source_Detail","Created as a forwarded signee in ZohoSign")
  185.                              r_CreateContact = zoho.crm.createRecord("Contacts",m_CreateContact)
  186.                              if(!isNull(r_CreateContact.get("id"))) 
  187.                              { 
  188.                                  v_MatchedContactID = r_CreateContact.get("id").toLong()
  189.                              } 
  190.                          } 
  191.                          // 
  192.                          m_UpdateDoc.put("Email",m_Notification.get("beneficiary_email"))
  193.                          m_UpdateDoc.put("zohosign__Contact",v_MatchedContactID)
  194.                      } 
  195.                      else if(m_Notification.get("operation_type") == "RequestRejected") 
  196.                      { 
  197.                          v_DocStatusSuffix = "DECLINED"
  198.                          b_UpdateRecipient = true
  199.                          v_RecipientEmail = m_Notification.get("performed_by_email")
  200.                          // 
  201.                          m_UpdateDoc.put("zohosign__Date_Declined",zoho.currentdate)
  202.                          m_UpdateDoc.put("zohosign__Declined_Reason",m_Notification.get("reason"))
  203.                      } 
  204.                      // 
  205.                      // finish updating the ZohoSign Document record 
  206.                      m_UpdateDoc.put("zohosign__Document_Status",v_DocStatusSuffix.proper())
  207.                      r_UpdateDoc = zoho.crm.updateRecord("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID,m_UpdateDoc)
  208.                      // 
  209.                      // create CRM zohosign document event 
  210.                      m_CreateEvent = Map()
  211.                      m_CreateEvent.put("Name",v_ZCRM_DocRecordName + " -" + v_DocStatusSuffix)
  212.                      m_CreateEvent.put("Owner",v_ZCRM_DocRecordOwnerID)
  213.                      if(m_Notification.get("performed_by_email") != "System Generated") 
  214.                      { 
  215.                          m_CreateEvent.put("Email",m_Notification.get("performed_by_email"))
  216.                      } 
  217.                      m_CreateEvent.put("zohosign__Date",zoho.currentdate)
  218.                      m_CreateEvent.put("zohosign__ZohoSign_Document",v_ZCRM_DocRecordID)
  219.                      m_CreateEvent.put("zohosign__Description",m_Notification.get("activity"))
  220.                      // 
  221.                      // create the event record 
  222.                      r_CreateEvent = zoho.crm.createRecord("zohosign__ZohoSign_Document_Events",m_CreateEvent)
  223.                      if(b_DebugMode) 
  224.                      { 
  225.                          v_DebugMessage = v_DebugMessage + "ZCRM Event Record: <br /><br />" + r_CreateEvent + "<hr />"
  226.                      } 
  227.                      // 
  228.                      // Fix for ZohoSign: if forwarded to another signee then completed, webhook on completed is not triggered??? 
  229.                      if(m_Request.get("request_status") == "completed") 
  230.                      { 
  231.                          m_UpdateDoc = Map()
  232.                          // 
  233.                          // time to complete 
  234.                          if(!isNull(v_ZCRM_DocCreated)) 
  235.                          { 
  236.                              v_DaysBetween = v_ZCRM_DocCreated.getPrefix("T").toDate().daysBetween(zoho.currentdate)
  237.                              v_DaysBetweenStr = if(v_DaysBetween == 1," day","days")
  238.                              m_UpdateDoc.put("zohosign__Time_to_complete",v_DaysBetweenStr)
  239.                          } 
  240.                          r_DocDetails = zoho.crm.getRecordById("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID)
  241.                          v_ExistingNotes = ifnull(r_DocDetails.get("zohosign__Document_Note"),"")
  242.                          l_ExistingNotes = v_ExistingNotes.toList("\n")
  243.                          l_ExistingNotes.add("Completed: " + zoho.currenttime.toString("yyyy-MM-dd HH:mm:ss","Europe/London"))
  244.                          m_UpdateDoc.put("zohosign__Document_Status","Completed")
  245.                          m_UpdateDoc.put("zohosign__Document_Note",l_ExistingNotes.toString("\n"))
  246.                          m_UpdateDoc.put("zohosign__Date_Completed",zoho.currentdate)
  247.                          r_UpdateDoc = zoho.crm.updateRecord("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID,m_UpdateDoc)
  248.                      } 
  249.                      // 
  250.                      // if we want to update the recipient record 
  251.                      if(b_UpdateRecipient) 
  252.                      { 
  253.                          // 
  254.                          // find the relevant recipient record 
  255.                          for each  m_Action in m_Request.get("actions") 
  256.                          { 
  257.                              if(!isNull(m_Action.get("action_status")) && v_RecipientEmail != "") 
  258.                              { 
  259.                                  v_ZCRM_RecipientRecordID = 0
  260.                                  l_SearchRecipients = zoho.crm.searchRecords("zohosign__ZohoSign_Recipients","(Email:equals:" + v_RecipientEmail + ")and(zohosign__ZohoSign_Document:equals:" + v_ZCRM_DocRecordID + ")")
  261.                                  for each  m_Recipient in l_SearchRecipients 
  262.                                  { 
  263.                                      if(!isNull(m_Recipient.get("id"))) 
  264.                                      { 
  265.                                          v_ZCRM_RecipientRecordID = m_Recipient.get("id").toLong()
  266.                                      } 
  267.                                  } 
  268.                                  if(v_ZCRM_RecipientRecordID == 0 && !m_Action.get("action_status").equalsIgnoreCase("FORWARDED")) 
  269.                                  { 
  270.                                      // 
  271.                                      // create CRM zohosign document recipient record 
  272.                                      m_CreateRecipient = Map()
  273.                                      m_CreateRecipient.put("Name",m_Action.get("recipient_name"))
  274.                                      m_CreateRecipient.put("Email",m_Action.get("recipient_email"))
  275.                                      m_CreateRecipient.put("Owner",v_ZCRM_DocRecordOwnerID)
  276.                                      m_CreateRecipient.put("zohosign__Recipient_Order",m_Action.get("signing_order"))
  277.                                      if(v_DocStatusSuffix == "COMPLETED") 
  278.                                      { 
  279.                                          m_CreateRecipient.put("zohosign__Date_Completed",zoho.currentdate)
  280.                                      } 
  281.                                      else if(v_DocStatusSuffix == "DECLINED") 
  282.                                      { 
  283.                                          m_CreateRecipient.put("zohosign__Date_Declined",zoho.currentdate)
  284.                                      } 
  285.                                      else 
  286.                                      { 
  287.                                          m_CreateRecipient.put("zohosign__Date_Delivered",zoho.currentdate)
  288.                                      } 
  289.                                      m_CreateRecipient.put("zohosign__ZohoSign_Document",v_ZCRM_DocRecordID)
  290.                                      v_RecipientType = "CC"
  291.                                      if(m_Action.get("action_type").equalsIgnoreCase("APPROVE")) 
  292.                                      { 
  293.                                          v_RecipientType = "Approver"
  294.                                      } 
  295.                                      else if(m_Action.get("action_type").equalsIgnoreCase("SIGN")) 
  296.                                      { 
  297.                                          v_RecipientType = "Signer"
  298.                                      } 
  299.                                      m_CreateRecipient.put("zohosign__Recipient_Type",v_RecipientType)
  300.                                      v_RecipientStatus = if(v_DocStatusSuffix == "SIGNED" || v_DocStatusSuffix == "APPROVED" || v_DocStatusSuffix == "FORWARDED" || v_DocStatusSuffix == "VIEWED","Delivered",v_DocStatusSuffix.proper())
  301.                                      m_CreateRecipient.put("zohosign__Recipient_Status",v_RecipientStatus)
  302.                                      if(v_ZS_DocID != 0) 
  303.                                      { 
  304.                                          m_CreateRecipient.put("zohosign__ZohoSign_Document_ID",v_ZS_DocID.toString())
  305.                                      } 
  306.                                      // 
  307.                                      // create the recipient record 
  308.                                      r_CreateRecipient = zoho.crm.createRecord("zohosign__ZohoSign_Recipients",m_CreateRecipient)
  309.                                      if(b_DebugMode) 
  310.                                      { 
  311.                                          v_DebugMessage = v_DebugMessage + "ZCRM Recipient Record - CREATE: <br />" + m_Action.get("recipient_name") + "<br />" + r_CreateRecipient + "<hr />"
  312.                                      } 
  313.                                  } 
  314.                                  else if(v_ZCRM_RecipientRecordID != 0 && m_Action.get("recipient_email").equalsIgnoreCase(m_Notification.get("performed_by_email"))) 
  315.                                  { 
  316.                                      // 
  317.                                      // update CRM zohosign document recipient record 
  318.                                      m_UpdateRecipient = Map()
  319.                                      m_UpdateRecipient.put("zohosign__Recipient_Order",m_Action.get("signing_order"))
  320.                                      v_RecipientType = "CC"
  321.                                      if(m_Action.get("action_type").equalsIgnoreCase("APPROVE")) 
  322.                                      { 
  323.                                          v_RecipientType = "Approver"
  324.                                      } 
  325.                                      else if(m_Action.get("action_type").equalsIgnoreCase("SIGN")) 
  326.                                      { 
  327.                                          v_RecipientType = "Signer"
  328.                                      } 
  329.                                      m_UpdateRecipient.put("zohosign__Recipient_Type",v_RecipientType)
  330.                                      v_RecipientStatus = if(v_DocStatusSuffix == "SIGNED" || v_DocStatusSuffix == "APPROVED" || v_DocStatusSuffix == "FORWARDED" || v_DocStatusSuffix == "VIEWED","Delivered",v_DocStatusSuffix.proper())
  331.                                      m_UpdateRecipient.put("zohosign__Recipient_Status",v_RecipientStatus)
  332.                                      if(v_DocStatusSuffix == "COMPLETED") 
  333.                                      { 
  334.                                          m_UpdateRecipient.put("zohosign__Date_Completed",zoho.currentdate)
  335.                                      } 
  336.                                      else if(v_DocStatusSuffix == "DECLINED") 
  337.                                      { 
  338.                                          m_UpdateRecipient.put("zohosign__Date_Declined",zoho.currentdate)
  339.                                      } 
  340.                                      else 
  341.                                      { 
  342.                                          m_UpdateRecipient.put("zohosign__Date_Delivered",zoho.currentdate)
  343.                                      } 
  344.                                      if(v_ZS_DocID != 0) 
  345.                                      { 
  346.                                          m_UpdateRecipient.put("zohosign__ZohoSign_Document_ID",v_ZS_DocID.toString())
  347.                                      } 
  348.                                      r_UpdateRecipient = zoho.crm.updateRecord("zohosign__ZohoSign_Recipients",v_ZCRM_RecipientRecordID,m_UpdateRecipient)
  349.                                      if(b_DebugMode) 
  350.                                      { 
  351.                                          v_DebugMessage = v_DebugMessage + "ZCRM Recipient Record - UPDATE: <br /><br />" + r_UpdateRecipient + "<hr />"
  352.                                      } 
  353.                                  } 
  354.                              } 
  355.                          } 
  356.                      } 
  357.                  } 
  358.              } 
  359.          } 
  360.          // 
  361.          // now attach to CRM Record if completed 
  362.          if(v_ZCRM_DocRecordID != 0 && v_DocStatusSuffix == "COMPLETED") 
  363.          { 
  364.              r_ZCRM_DocumentDetails = zoho.crm.getRecordById("zohosign__ZohoSign_Documents",v_ZCRM_DocRecordID)
  365.              if(!isNull(r_ZCRM_DocumentDetails.get("zohosign__Module_Record_ID"))) 
  366.              { 
  367.                  // 
  368.                  // get module and record ID 
  369.                  v_ModuleName = ifnull(r_ZCRM_DocumentDetails.get("zohosign__Module_Name"),"-")
  370.                  v_ModuleRecordID = r_ZCRM_DocumentDetails.get("zohosign__Module_Record_ID")
  371.                  v_ZS_DocumentName = ifnull(r_ZCRM_DocumentDetails.get("Name"),v_ModuleName)
  372.                  // 
  373.                  // download file 
  374.                  r_ZS_Download = zoho.sign.downloadDocument(v_ZS_RequestID)
  375.                  v_Filename = v_ZS_DocumentName; 
  376.                  if(!isNull(v_ZS_RequestName)) 
  377.                  { 
  378.                      v_Filename = v_ZS_RequestName; 
  379.                  } 
  380.                  if(v_Filename.lastIndexOf(".") > 0) 
  381.                  { 
  382.                      v_Filename = v_Filename.subString(0,v_Filename.lastIndexOf("."))
  383.                  } 
  384.                  r_ZS_Download.setFileName(v_Filename + "_SIGNED.pdf")
  385.                  // 
  386.                  // attach file to record of module 
  387.                  r_AttachFile = zoho.crm.attachFile(v_ModuleName,v_ModuleRecordID,r_ZS_Download)
  388.                  // 
  389.                  // for debug purposes but completely unnecessary for the function of this function 
  390.                  if(!isNull(r_AttachFile.get("message"))) 
  391.                  { 
  392.                      v_DebugMessage = v_DebugMessage + "CRM Attachment: <br /><br />" + r_AttachFile.get("message") + "<hr />"
  393.                  } 
  394.              } 
  395.          } 
  396.      } 
  397.  } 
  398.  v_DebugMessage = v_DebugMessage + "Date/Time: <br /><br />" + zoho.currenttime
  399.  // 
  400.  if(b_DebugMode) 
  401.  { 
  402.      sendmail 
  403.      [ 
  404.          from :zoho.adminuserid 
  405.          to :"me+This email address is being protected from spambots. You need JavaScript enabled to view it." 
  406.          subject :"My ZohoSign Webhook - ANY EVENT" 
  407.          message :v_DebugMessage 
  408.      ] 
  409.  } 
  410.  return ""

Related List for Custom Module
Just for good measure (and because it took me an hour to write this), here's the code to generate a related list on a custom module to the related ZohoSign Documents in ZohoCRM:
copyraw
/* *******************************************************************************
Function:       String fn_Maintenance_ZohoSignRelatedList(Int p_ThisRecordID)
Label:          Fn - Maintenance - Related List - Zoho Sign Documents
Trigger:        On load of a maintenance record
Purpose:	    Function used to generate a related list of ZohoSign Documents
Inputs:         Record ID
Outputs:        XML related list

Date Created:   2024-08-29 (Joel Lipman)
                - Initial release
Date Modified:	???
                - ???
******************************************************************************* */
// 
// initialize 
v_Index = 0;
v_RelatedListXML = "";
v_ModuleName = "Maintenance";
v_CrmOrgID = "org123456789";
v_ZohoSignDocModuleName = "CustomModule12";
v_DateFormat = "dd MMM, yyyy";
v_TimeFormat = v_DateFormat + " HH:mm:ss";
// 
// search in CRM for ZohoSign Documents
l_SearchResults = zoho.crm.searchRecords("zohosign__ZohoSign_Documents","(zohosign__Module_Name:equals:" + v_ModuleName + ")and(zohosign__Module_Record_ID:equals:" + p_ThisRecordID + ")");
// 
// if found records 
if(l_SearchResults.size() > 0 && l_SearchResults.toString().contains("zohosign__ZohoSign_Document_ID"))
{
	v_RelatedListXML = "<record>";
	for each  m_Result in l_SearchResults
	{
		// init 
		v_Index = v_Index + 1;
		v_RelatedListXML = v_RelatedListXML + "<row no=\"" + v_Index + "\">";
		// 
		// retrieve/transform values from record
		v_ZohoSignDoc_ID = m_Result.get("id");
		v_ZohoSignDoc_Name = m_Result.get("Name");
		v_LinkName1 = "https://crm.zoho.com/crm/" + v_CrmOrgID + "/tab/" + v_ZohoSignDocModuleName + "/" + v_ZohoSignDoc_ID;
		if(!isNull(m_Result.get("zohosign__Contact")))
		{
			v_ZohoSignDoc_ContactID = m_Result.get("zohosign__Contact").get("id");
			v_ZohoSignDoc_ContactName = m_Result.get("zohosign__Contact").get("name");
			v_LinkName2 = "https://crm.zoho.com/crm/" + v_CrmOrgID + "/tab/Contacts/" + v_ZohoSignDoc_ContactID;
		}
		else
		{
			v_ZohoSignDoc_ContactName = m_Result.get("Email");
			v_LinkName2 = "mailto:" + m_Result.get("Email");
		}
		v_ZohoSignDoc_Status = ifnull(m_Result.get("zohosign__Document_Status"),"-");
		v_ZohoSignDoc_DateSent = if(!isNull(m_Result.get("zohosign__Date_Sent")),m_Result.get("zohosign__Date_Sent").toString(v_DateFormat),"-");
		v_ZohoSignDoc_DateCompleted = if(!isNull(m_Result.get("zohosign__Date_Completed")),m_Result.get("zohosign__Date_Completed").toString(v_DateFormat),"-");
		v_ZohoSignDoc_DateDeclined = if(!isNull(m_Result.get("zohosign__Date_Declined")),m_Result.get("zohosign__Date_Declined").toString(v_DateFormat),"-");
		v_ZohoSignDoc_LastActivityTime = if(!isNull(m_Result.get("Last_Activity_Time")),m_Result.get("Last_Activity_Time").replaceAll("T"," ").getPrefix("+").toString(v_TimeFormat),"-");
		//
		// generate XML related list response
		// don't forget to escape quote name with CDATA in case of XML banned characters 
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Document Name\" link=\"true\" url=\"" + v_LinkName1 + "\"><![CDATA[" + v_ZohoSignDoc_Name + "]]></FL>";
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Contact\" link=\"true\" url=\"" + v_LinkName2 + "\"><![CDATA[" + v_ZohoSignDoc_ContactName + "]]></FL>";
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Document Status\">" + v_ZohoSignDoc_Status + "</FL>";
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Date Sent\">" + v_ZohoSignDoc_DateSent + "</FL>";
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Date Completed\">" + v_ZohoSignDoc_DateCompleted + "</FL>";
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Date Declined\">" + v_ZohoSignDoc_DateDeclined + "</FL>";
		v_RelatedListXML = v_RelatedListXML + "<FL val=\"Last Activity Time\">" + v_ZohoSignDoc_LastActivityTime + "</FL>";
		v_RelatedListXML = v_RelatedListXML + "</row>";
	}
	v_RelatedListXML = v_RelatedListXML + "</record>";
}
else
{
	// display "no records found" 
	v_RelatedListXML = "<error><message>";
	v_RelatedListXML = v_RelatedListXML + "No records found";
	v_RelatedListXML = v_RelatedListXML + "</message></error>";
}
// 
// output 
return v_RelatedListXML;
  1.  /* ******************************************************************************* 
  2.  Function:       string fn_Maintenance_ZohoSignRelatedList(Int p_ThisRecordID) 
  3.  Label:          Fn - Maintenance - Related List - Zoho Sign Documents 
  4.  Trigger:        On load of a maintenance record 
  5.  Purpose:        Function used to generate a related list of ZohoSign Documents 
  6.  Inputs:         Record ID 
  7.  Outputs:        XML related list 
  8.   
  9.  Date Created:   2024-08-29 (Joel Lipman) 
  10.                  - Initial release 
  11.  Date Modified:    ??? 
  12.                  - ??? 
  13.  ******************************************************************************* */ 
  14.  // 
  15.  // initialize 
  16.  v_Index = 0
  17.  v_RelatedListXML = ""
  18.  v_ModuleName = "Maintenance"
  19.  v_CrmOrgID = "org123456789"
  20.  v_ZohoSignDocModuleName = "CustomModule12"
  21.  v_DateFormat = "dd MMM, yyyy"
  22.  v_TimeFormat = v_DateFormat + " HH:mm:ss"
  23.  // 
  24.  // search in CRM for ZohoSign Documents 
  25.  l_SearchResults = zoho.crm.searchRecords("zohosign__ZohoSign_Documents","(zohosign__Module_Name:equals:" + v_ModuleName + ")and(zohosign__Module_Record_ID:equals:" + p_ThisRecordID + ")")
  26.  // 
  27.  // if found records 
  28.  if(l_SearchResults.size() > 0 && l_SearchResults.toString().contains("zohosign__ZohoSign_Document_ID")) 
  29.  { 
  30.      v_RelatedListXML = "<record>"
  31.      for each  m_Result in l_SearchResults 
  32.      { 
  33.          // init 
  34.          v_Index = v_Index + 1
  35.          v_RelatedListXML = v_RelatedListXML + "<row no=\"" + v_Index + "\">"
  36.          // 
  37.          // retrieve/transform values from record 
  38.          v_ZohoSignDoc_ID = m_Result.get("id")
  39.          v_ZohoSignDoc_Name = m_Result.get("Name")
  40.          v_LinkName1 = "https://crm.zoho.com/crm/" + v_CrmOrgID + "/tab/" + v_ZohoSignDocModuleName + "/" + v_ZohoSignDoc_ID; 
  41.          if(!isNull(m_Result.get("zohosign__Contact"))) 
  42.          { 
  43.              v_ZohoSignDoc_ContactID = m_Result.get("zohosign__Contact").get("id")
  44.              v_ZohoSignDoc_ContactName = m_Result.get("zohosign__Contact").get("name")
  45.              v_LinkName2 = "https://crm.zoho.com/crm/" + v_CrmOrgID + "/tab/Contacts/" + v_ZohoSignDoc_ContactID; 
  46.          } 
  47.          else 
  48.          { 
  49.              v_ZohoSignDoc_ContactName = m_Result.get("Email")
  50.              v_LinkName2 = "mailto:" + m_Result.get("Email")
  51.          } 
  52.          v_ZohoSignDoc_Status = ifnull(m_Result.get("zohosign__Document_Status"),"-")
  53.          v_ZohoSignDoc_DateSent = if(!isNull(m_Result.get("zohosign__Date_Sent")),m_Result.get("zohosign__Date_Sent").toString(v_DateFormat),"-")
  54.          v_ZohoSignDoc_DateCompleted = if(!isNull(m_Result.get("zohosign__Date_Completed")),m_Result.get("zohosign__Date_Completed").toString(v_DateFormat),"-")
  55.          v_ZohoSignDoc_DateDeclined = if(!isNull(m_Result.get("zohosign__Date_Declined")),m_Result.get("zohosign__Date_Declined").toString(v_DateFormat),"-")
  56.          v_ZohoSignDoc_LastActivityTime = if(!isNull(m_Result.get("Last_Activity_Time")),m_Result.get("Last_Activity_Time").replaceAll("T"," ").getPrefix("+").toString(v_TimeFormat),"-")
  57.          // 
  58.          // generate XML related list response 
  59.          // don't forget to escape quote name with CDATA in case of XML banned characters 
  60.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Document Name\" link=\"true\" url=\"" + v_LinkName1 + "\"><![CDATA[" + v_ZohoSignDoc_Name + "]]></FL>"
  61.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Contact\" link=\"true\" url=\"" + v_LinkName2 + "\"><![CDATA[" + v_ZohoSignDoc_ContactName + "]]></FL>"
  62.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Document Status\">" + v_ZohoSignDoc_Status + "</FL>"
  63.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Date Sent\">" + v_ZohoSignDoc_DateSent + "</FL>"
  64.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Date Completed\">" + v_ZohoSignDoc_DateCompleted + "</FL>"
  65.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Date Declined\">" + v_ZohoSignDoc_DateDeclined + "</FL>"
  66.          v_RelatedListXML = v_RelatedListXML + "<FL val=\"Last Activity Time\">" + v_ZohoSignDoc_LastActivityTime + "</FL>"
  67.          v_RelatedListXML = v_RelatedListXML + "</row>"
  68.      } 
  69.      v_RelatedListXML = v_RelatedListXML + "</record>"
  70.  } 
  71.  else 
  72.  { 
  73.      // display "no records found" 
  74.      v_RelatedListXML = "<error><message>"
  75.      v_RelatedListXML = v_RelatedListXML + "No records found"
  76.      v_RelatedListXML = v_RelatedListXML + "</message></error>"
  77.  } 
  78.  // 
  79.  // output 
  80.  return v_RelatedListXML; 

Category: Zoho :: Article: 881

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.