Embedded signing in Zoho Sign

Zoho Sign helps users to sign directly through a third-party app or website with embedded signing. This can help users to automate workflows based on their business needs, and collect signers from clients without asking them to leave your website or app to complete the signing process.

Embedding signing allows embedding the signing link directly into the website or the app by generating signing URLs through Zoho Sign, authenticating your recipients, presenting the signing request, and redirecting once the transaction is complete.

Implementing embedded signing in your workflow will help you in collecting signatures from many recipients in the correct order and promptly.

There are two ways to implement embedded signing :

  • Open the Zoho Sign signing page in a new window or tab
  • Open the Zoho Sign signing page in an iframe within your application

When either method is implemented, a unique signing URL will be generated, which will be valid for two minutes. If the signing page is not open by then, a new link needs to be generated and it is a one-time usable URL.

API Details:

Note:

  • In the "data" param you send in either of the above API's, you need to send an additional key is_embedded as true inside actions for the recipients you need to sign within your application and needs to be added before submitting the document.
  • The action_type must be only SIGN.
  • Signing link via email/SMS and reminder notification will not be sent to the recipients who are mapped as Embedded recipients.
  • To host an embedded signing URL in your iframe, the host parameter must be passed. Eg: ....embedtoken?host=https://example.com
  • If you would like to configure redirection of the page after the embedded signing has taken place, you may send an additional JSON object with name redirect_pages within "data" inside the requests object.

Redirect pages JSON format:

sign_success url to redirect when signing has been done but the document is not completed
sign_completed url to redirect when the signing has been done and the document is completed
sign_declined url to redirect when signer has declined signing
sign_later 1url to redirect when the signer has chosen not to sign at present
Copied<?php
//Create document and add recipients
$actionsJson= new stdClass();
$actionsJson->recipient_name = "<Recipient-Name>";
$actionsJson->recipient_email = "<Recipient-Email>";
$actionsJson->action_type = "SIGN";
$actionsJson->private_notes = "Please get back to us for further queries";
$actionsJson->signing_order = 0;
$actionsJson->verify_recipient = true;
$actionsJson->verification_type = "EMAIL";
//set is_embedded as true for generating embedded signing url
$actionsJson->is_embedded = true;

$requestJSON= new stdClass();
$requestJSON->request_name = "<Request-Name>";
$requestJSON->expiration_days = 1;
$requestJSON->is_sequential = true;
$requestJSON->email_reminders = true;
$requestJSON->reminder_period = 8;
$requestJSON->actions = array($actionsJson);

$request= new stdClass();
$request->requests = $requestJSON;
$data = json_encode($request);
$POST_DATA = array(
    'data' => $data,
    'file' => new CURLfile("<File-Path>")
);
$curl = curl_init("https://sign.zoho.com/api/v1/requests");
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
    'Authorization:Zoho-oauthtoken <Oauth Token>',
    "Content-Type:multipart/form-data"
));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $POST_DATA);
$response = curl_exec($curl);
$jsonbody = json_decode($response); // contain filed tyes response
if ($jsonbody->status == "success") {
    $createdRequestId = $jsonbody->requests->request_id;
    //Save the ID from the response to update later
    $createdRequest=new stdClass();
    $createdRequest = $jsonbody->requests;
} else //Error check for error
{
    echo $jsonbody->message;
}
curl_close($curl);


// //Add fields and send out document

$actionsJson=new stdClass();
$actionsJson->action_id = $createdRequest->actions[0]->action_id;
$actionsJson->recipient_name = $createdRequest->actions[0]->recipient_name;
$actionsJson->recipient_email = $createdRequest->actions[0]->recipient_email;
$actionsJson->action_type = $createdRequest->actions[0]->action_type;

$fieldJson=new stdClass();
$fieldJson->document_id = $createdRequest->document_ids[0]->document_id;
$fieldJson->field_name = "TextField";
$fieldJson->field_type_name = "Textfield";
$fieldJson->field_label = "Text - 1";
$fieldJson->field_category = "Textfield";
$fieldJson->abs_width = "200";
$fieldJson->abs_height = "18";
$fieldJson->is_mandatory = true;
$fieldJson->x_coord = "30";
$fieldJson->y_coord = "30";
$fieldJson->page_no = 0;

$actionsJson->fields = array($fieldJson);

$createdRequest=new stdClass();
$createdRequest->actions = array($actionsJson);

$request=new stdClass();
$request->requests = $createdRequest1;

$requestData = json_encode($request1);
$POST_DATA = array(
    'data' => $requestData
);
$curl = curl_init("https://sign.zoho.com/api/v1/requests/".$createdRequestId."/submit");
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
    'Authorization:Zoho-oauthtoken <Oauth Token>',
));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $POST_DATA);
$response = curl_exec($curl);
echo $response;
$jsonbody = json_decode($response); // contain filed tyes response
if ($jsonbody->status == "success") {
    $created_request_id = $jsonbody->requests->request_id; //Save the ID from the response to update later
    $action_id=$jsonbody->requests->actions[0]->action_id;
    $status = $jsonbody->requests->request_status;
    echo $status;
} 
else //Error check for error
{
    echo $jsonbody->message;
}
curl_close($curl);

$POST_DATA = array(
    'host' => https://sign.zoho.com
);
$curl = curl_init("https://sign.zoho.com/api/v1/requests/".$createdRequestId."/actions/".$action_id."/embedtoken");
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
    'Authorization:Zoho-oauthtoken <Oauth Token>',
));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $POST_DATA);
$response = curl_exec($curl);
echo $response;
$jsonbody = json_decode($response); // contain filed tyes response
if ($jsonbody->status == "success") {
    $signingURL = $jsonbody->sign_url;
    echo $signingURL;
} 
else //Error check for error
{
    echo $jsonbody->message;
}
curl_close($curl);
?>
CopiedString access_token="<access_token>";
        HttpClient client = createDefault();
        File firstFile = new File("/home/documents/Test1.pdf"); //First file to be uploaded
        File secondFile = new File("/home/documents/Test2.pdf"); //Second file to be uploaded

        //Documents json object with details about the document and recipients
        JSONObject docJson = new JSONObject();
        docJson.put("request_name","testDoc");
        docJson.put("expiration_days",10);
        docJson.put("is_sequential", true);
        docJson.put("notes","Sign this test sign");
        JSONObject actionJson = new JSONObject();
        actionJson.put("action_type","SIGN");
        actionJson.put("recipient_email","<enter recipient's email address>");
        actionJson.put("recipient_name","<enter recipient's name>");
        actionJson.put("verify_recipient", false);
        actionJson.put("is_embedded",true);//Set this only for the recipient who will sign directly in your portal.

        docJson.put("actions", new JSONArray().put(actionJson));
        JSONObject dataJson =new JSONObject();
        dataJson.put("requests", docJson);
        //dataJson=docJson;
        MultipartEntityBuilder reqEntity = MultipartEntityBuilder.create();
        reqEntity.addBinaryBody("file",firstFile, ContentType.APPLICATION_OCTET_STREAM, "Test1.pdf");
        reqEntity.addBinaryBody("file", secondFile, ContentType.APPLICATION_OCTET_STREAM, "Test2.pdf");
        reqEntity.addTextBody("data",dataJson.toString());
        HttpEntity multipart = reqEntity.build();
        HttpPost postMethod = new HttpPost("https://sign.zoho.com/api/v1/requests");
        postMethod.setHeader("Authorization","Zoho-oauthtoken "+ access_token);
        postMethod.setEntity(multipart);
        HttpResponse response = null;
        try {
            response = client.execute(postMethod);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String responseJSON = null;
        try {
            responseJSON = EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.println("The doc upload response is :"+responseJSON);
        JSONObject documentJson = new JSONObject(responseJSON);

        if(documentJson.getString("status").equals("success"))
            {   JSONObject requestsObj = documentJson.getJSONObject("requests");
                String requestId = requestsObj.getString("request_id");
                JSONArray actions = requestsObj.getJSONArray("actions");
                JSONObject submitRequestObj = new JSONObject();
                submitRequestObj.put("request_name", requestsObj.getString("request_name"));
                JSONArray submitActions = new JSONArray();
                //Add fields to recipient
                // Alternatively, you may use placeholders for adding fields. Refer https://www.zoho.com/sign/help/auto-place-text-tags.html
                 for(int i=0;i< actions.length() ;i++)
                 { 
	 JSONObject submitAction = new JSONObject();
	 JSONObject action = actions.getJSONObject(i);
	 submitAction.put("recipient_name", action.getString("recipient_name"));
	 submitAction.put("recipient_email", action.getString("recipient_email"));
	 submitAction.put("action_type", action.getString("action_type"));
	 submitAction.put("action_id", action.getString("action_id"));
	 
                     if(action.getString("action_type").equals("SIGN"))
                     { JSONArray fields = new JSONArray();
                         JSONArray documents = requestsObj.getJSONArray("document_ids");
                         for(int j=0; j< documents.length(); j++)
                         { String document_id = documents.getJSONObject(j).getString("document_id");
                             JSONObject fieldJson = new JSONObject(); //Add a signature field
                             fieldJson.put("field_name","Signature");
                             fieldJson.put("field_label","Signature");
                             fieldJson.put("field_type_name","Signature");
                             fieldJson.put("field_category","image");
                             fieldJson.put("page_no",0);
                             fieldJson.put("x_coord",10+i*100);
                             fieldJson.put("y_coord",10+i*100);
                             fieldJson.put("abs_width", 100);
                             fieldJson.put("abs_height", 40);
                             fieldJson.put("document_id",document_id); 
                             fields.put(fieldJson); 
                             //Add a date field
                             fieldJson = new JSONObject();
                             fieldJson.put("field_name","Sign Date");
                             fieldJson.put("field_label","Sign Date");
                             fieldJson.put("field_type_name","Date");
                             fieldJson.put("field_category","datefield");
                             fieldJson.put("page_no",0);
                             fieldJson.put("x_coord",10+i*100);
                             fieldJson.put("y_coord",40+i*100);
                             fieldJson.put("abs_width", 100);
                             fieldJson.put("abs_height", 40);
                             fieldJson.put("document_id",document_id);
                             fields.put(fieldJson);
                             submitAction.put("fields",fields);
                         }
                     }
                     submitActions.put(submitAction);
                 }
                 submitRequestObj.put("actions",submitActions);
                 dataJson = new JSONObject();
                 dataJson.put("requests", submitRequestObj);
                 reqEntity = MultipartEntityBuilder.create();
                 reqEntity.addTextBody("data",dataJson.toString());
                 System.out.println("The submit req payload is :"+dataJson);
                 postMethod = new HttpPost("https://sign.zoho.com/api/v1/requests/"+ requestId+"/submit");
                 postMethod.setHeader("Authorization","Zoho-oauthtoken "+ access_token);
                 multipart = reqEntity.build();
                 postMethod.setEntity(multipart);
                try {
                    response = client.execute(postMethod);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                try {
                    responseJSON = EntityUtils.toString(response.getEntity(), "UTF-8");
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                JSONObject submitResponse = new JSONObject(responseJSON);
                System.out.println("The submit response is :"+submitResponse);
                 if(submitResponse.has("status") && submitResponse.getString("status").equals("success"))
                 { System.out.println("Document sent successfully");
                     //To get the signing URL
                     for(int i=0;i< actions.length() ;i++)
                     { JSONObject action = actions.getJSONObject(i);
                         String actionId = action.getString("action_id");
                         postMethod = new HttpPost("https://sign.zoho.com/api/v1/requests/"+ requestId+"/actions/"+actionId+"/embedtoken");
                         postMethod.setHeader("Authorization","Zoho-oauthtoken "+ access_token);
                         try {
                             response = client.execute(postMethod);
                         } catch (IOException e) {
                             throw new RuntimeException(e);
                         }
                         try {
                             responseJSON = EntityUtils.toString(response.getEntity(), "UTF-8");
                         } catch (IOException e) {
                             throw new RuntimeException(e);
                         }
                         JSONObject tokenResponse = new JSONObject(responseJSON);
                         if(tokenResponse.has("status") && tokenResponse.getString("status").equals("success"))
                         { System.out.println("Signing URL retrieved"); String signingUrl = tokenResponse.getString("sign_url");
                             //open this signingUrl in iframe or new tab/window
                             System.out.println("Signing URL : "+signingUrl);
                             } else
                             { System.out.println("Error : "+tokenResponse.getString("message")); }
                     }
                 }
                 else { System.out.println("Error : "+submitResponse.getString("message")); }
            }
        else
        { System.out.println("Error with creating document : "+documentJson.getString("message")); }
Copiedconst express = require('express');
const fs = require('fs');
const FormData = require('form-data');
const fetch = require('node-fetch');
const path = require('path');

const app = express();
const port = 4000;
app.get('/embeddedsigning', async (req, res) => {
    let actionsJson = {};
    actionsJson['recipient_name'] = '<Recipient-Name>';
    actionsJson['recipient_email'] = '<Recipient-Email>';
    actionsJson['action_type'] = 'SIGN';
    actionsJson['private_notes'] = 'Please get back to us for further queries';
    actionsJson['signing_order'] = 0;
    actionsJson['verify_recipient'] = true;
    actionsJson['verification_type'] = 'EMAIL';
    actionsJson['is_embedded']=true;

    let documentJson = {};
    documentJson['request_name'] = '<Request-Name>';
    documentJson['expiration_days'] = 1;
    documentJson['is_sequential'] = true;
    documentJson['email_reminders'] = true;
    documentJson['reminder_period'] = 8;
    documentJson['actions'] = new Array(actionsJson);

    let data = {};
    data['requests'] = documentJson;

    let files = ['<File-Path>'];
    var payload = new FormData();
    if (fs.existsSync(files[0])) {
        let value = fs.createReadStream(files[0]);
        payload.append('file', value);
    } else {
        return 'unable to read file';
    }
    payload.append('data', JSON.stringify(data));
    let HEADERS = {};
    HEADERS['Authorization'] = 'Zoho-oauthtoken <Oauth Token>';
    let URL = 'https://sign.zoho.com/api/v1/requests';
    let method = 'POST';
    let requestOptions = {
        method: method,
        headers: HEADERS,
        body: payload
    };

    let response = await fetch(URL, requestOptions)
        .then((_res) => {
            console.log(`Return code is ${_res.status}`);
            return _res.json().then((responseJson) => {
                return responseJson['requests'];
            });
        })
        .catch((error) => {
            let errorResponse = {};
            errorResponse['message'] = 'call failed to initiate'; //check internet connection or proper DC type
            errorResponse['status'] = 'failure';
            return errorResponse;
        });

    var request_id = response.request_id;
    var action_id=response.actions[0].action_id;
    let actionsJson1 = {};
    actionsJson1['action_id'] = response.actions[0].action_id;
    actionsJson1['recipient_name'] = response.actions[0].recipient_name;
    actionsJson1['recipient_email'] = response.actions[0].recipient_email;
    actionsJson1['action_type'] = response.actions[0].action_type;

    let fieldJson = {};
    fieldJson['document_id'] = response.document_ids[0].document_id;
    fieldJson['field_name'] = 'TextField';
    fieldJson['field_type_name'] = 'Textfield';
    fieldJson['field_label'] = 'Text - 1';
    fieldJson['field_category'] = 'Textfield';
    fieldJson['abs_width'] = '200';
    fieldJson['abs_height'] = '18';
    fieldJson['is_mandatory'] = true;
    fieldJson['x_coord'] = '30';
    fieldJson['y_coord'] = '30';
    fieldJson['page_no'] = 0;

    actionsJson1['fields'] = new Array(fieldJson);
    let documentJson1 = {};
    documentJson1['actions'] = new Array(actionsJson1);
    let data1 = {};
    data1['requests'] = documentJson1;
    var payload1 = new FormData();
    payload1.append('data', JSON.stringify(data1));
    let URL1 = 'https://sign.zoho.com/api/v1/requests/' + request_id + '/submit';
    let requestOptions1 = {
        method: 'POST',
        headers: HEADERS,
        body: payload1
    };
    let response1 = await fetch(URL1, requestOptions1)
        .then((_res1) => {
            console.log(`Return code is ${_res1.status}`);
            return _res1.json().then((responseJson1) => {
                return responseJson['requests'];
            });
        })
        .catch((error) => {
            let errorResponse = {};
            errorResponse['message'] = 'call failed to initiate'; //check internet connection or proper DC type
            errorResponse['status'] = 'failure';
            return errorResponse;
        });

        var payload = new FormData();
        payload.append('host', "https://sign.zoho.com");
        let URL2 = 'https://sign.zoho.com/api/v1/requests/' + request_id + '/actions/'+action_id+"/embedtoken";
        let requestOptions2 = {
            method: 'POST',
            headers: HEADERS,
            body: payload
        };
        return fetch(URL2, requestOptions2)
            .then((_res1) => {
                console.log(`Return code is ${_res1.status}`);
                return _res1.json().then((responseJson1) => {
                    res.send(responseJson1.sign_url);
                });
            })
            .catch((error) => {
                let errorResponse = {};
                errorResponse['message'] = 'call failed to initiate'; //check internet connection or proper DC type
                errorResponse['status'] = 'failure';
                return errorResponse;
            });
});
app.listen(port, () => {
    console.log(`Example app listening on port  {port}`);
});