Creating and managing signing requests

  1. Overview
  2. Creating a basic signing request
  3. Activating a signing request
  4. Working with multiple documents
  5. Using different authentication types
  6. Options for signing order
  7. Working with signing requests after activation

1. Overview

This tutorial helps you create and manage Zefort Sign signing requests via API.

Zefort Sign allows users to electronically sign documents and review related attachments. The signing can be done with either a basic authentication (SMS or email) or strong authentication (Bank ID or similar).

The signing request can include multiple parties with different roles. You can also define the order in which the document is signed or reviewed.

2. Creating a basic signing request

Every signing request must include a document to be signed and one or more parties that receive the request.

The document must be stored in Zefort before creating the request. The parties may be Zefort users or external users.

The request is initially created in a draft state and must be separately activated before it is sent out to the receiving parties.

Key parameters:

contract Zefort’s unique ID for the contract to be signed.
parties This section identifies all individual recipients of the signing request. Typically all parties are added when creating the signing request. However, you can also add parties one by one later with a separate API request.
name, email, user

name and email are mandatory fields that identify the party.

However, if the recipient has a Zefort account, you can replace name and email with `user along with the user’s Zefort user ID. In this case, the name and email will be populated from Zefort’s user account records.

role

Either signer, approver or cc

signer and approver sign or approve the document using the selected authentication method.

cc role does not actually sign the document but gets status updates on the progress of the signing process.


import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
payload = {
    "contract": CONTRACT_ID,
    "parties": [
        {
            "name": "John Doe",
            "email": "john.doe@mycompany.com",
            "role": "signer",
            "authentication_type": "email",
        },
        {
            "name": "Jane Doe",
            "email": "jane.doe@customer.com",
            "role": "signer",
            "authentication_type": "email",
        },
        {
            "user": "user_1Kcre8baSbmRNhTQIG",
            "role": "approver",
            "authentication_type": "email",
        }
    ]
}

response = requests.post("https://sandbox.zefort.com/api/esigns/", 
                         auth=(ZEFORT_APIKEY, ""),
                         json=payload)

print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."

curl -X POST "https://sandbox.zefort.com/api/esigns/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"contract": "ct_1JrjJRTzEMlHVHls", "parties": [{"name": "John Doe", "email": "john.doe@mycompany.com", "role": "signer", "authentication_type": "email"},{"name": "Jane Doe", "email": "jane.doe@customer.com", "role": "signer", "authentication_type": "email"},{"user": "user_1Kcre8baSbmRNhTQIG", "role": "approver", "authentication_type": "email"}]}'


Response:

{
    "id": "esgn_lHVHls1JrjJRTzEM",
    "owner": {...},
    "contract": "ct_1JrjJRTzEMlHVHls",
    "contract_binders": [],
    "title": "",
    "sign_order": "parallel",
    "greetings": null,
    "need_auth_to_view_documents": false,
    "signed": false,
    "status": "draft",
    "activated": false,
    ...
    "parties": [
        {
            "id": "sgnr_JrjJRTzEMlHVHls1",
            "name": "John Doe",
            "additional_info": null,
            "email": "john.doe@mycompany.com",
            "phone_number": null,
            "language": "en-us",
            "personal_id": null,
            "status": "draft",
            "role": "signer",
            "ordinal": 2,
            "authentication_type": "email",
            "need_auth_to_view_documents": false,
            ...
        },
        ...
    ],
    "documents": [
        {
            "id": "esdc_1KgKFkdqWRU7keVFq5",
            "content_type": "application/pdf",
            "filename": "test.pdf",
            "signable": true,
            "needs_to_be_viewed": true
        }
    ]
}

3. Activating a signing request

The signing request is initially created in a draft state and must be separately activated. Once activated, the signing requests will be sent to all parties whose signing or approval is not completed.

If you want to add signing parties after creating the signing request, you should use own endpoints for each additional party (/api/esign/<id>/parties/)

import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"

response = requests.post(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/activate/", 
    auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID="esgn_lHVHls1JrjJRTzEM"

curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/activate/" \
     -u $ZEFORT_APIKEY:


4. Working with multiple documents

Signing requests can only have a single document to be signed. However, the request can include multiple documents used as attachments. While the attachments are not signed, their filenames and checksums will be included in the signed document.

The easiest way to add attachments is to add them to the main contract stored in Zefort. In this case, the main document will be signed and the other documents will be shown as attachments.

You can also explicitly pick documents from other contracts stored in Zefort. For example, if you have a separate General Terms and Conditions document, you can re-use that for your signing request.

Key parameters:

contract Mandatory: Zefort’s unique ID for the contract to be signed. If the contract has multiple documents, the main document will be signed and the other documents will be automatically included as attachments.
document Optional: Explicitly defines the document to be signed. If document is given, no attachments will be added automatically. Document needs to belong to given contract.
attachments Optional: Additional documents stored in Zefort. These can belong to any contract stored in Zefort.
needs_to_be_viewed If set to true all attachments must be opened and viewed before signing or approval is possible. The default value is false meaning that only the signed document must be opened and viewed.
import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
DOCUMENT_ID = "doc_2VHlsJrjJRTzEMlH"
ATTACHMENT1_ID = "doc_5zEMlHVHlsJrjJRT"
ATTACHMENT2_ID = "doc_7RTzEMlHVHlsJrjJ"
payload = {
    "contract": CONTRACT_ID,
    "document": DOCUMENT_ID,
    "attachments": [ATTACHMENT1_ID, ATTACHMENT2_ID],
    "needs_to_be_viewed": True,
}

response = requests.post("https://sandbox.zefort.com/api/esigns/", 
                         auth=(ZEFORT_APIKEY, ""),
                         json=payload)

print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
curl -X POST "https://sandbox.zefort.com/api/esigns/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"contract": "ct_1JrjJRTzEMlHVHls", "document": "doc_2VHlsJrjJRTzEMlH", "attachments": ["doc_5zEMlHVHlsJrjJRT", "doc_7RTzEMlHVHlsJrjJ"], "needs_to_be_viewed": true}'


5. Using different authentication types

Zefort Sign provides several authentication methods for identifying the users signing or approving documents.

SMS and email authentication are so-called basic authentication methods. Strong authentication methods include Bank ID authentication or other country-specific authentication methods.

You can define the authentication type individually for each party in the signing request.

Key parameters:

authentication_type

email: Standard authentication method.

sms: This authentication method requires you to define the recipient's phone_number.

finnish_trust_network, strong_auth_: Defines the strong authentication method used.

personal_id Optional parameter for strong authentication. If defined, the value received from the external authentication service must match this value to authenticate the user.
need_auth_to_view_documents Optional parameter. When set to true, all parties have to use either SMS or strong authentication to view or sign the document.


NOTE: As new authentication types are added continuously, please refer to the technical API documentation for a complete list of authentication types.

import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
payload = {
    "contract": CONTRACT_ID,
    "need_auth_to_view_documents": True,
    "parties": [
        {
            "name": "John Doe",
            "email": "john.doe@mycompany.com",
            "role": "signer",
            "authentication_type": "sms",
            "phone_number": "+3581234567",
        },
        {
            "name": "Jane Doe",
            "email": "jane.doe@customer.com",
            "role": "signer",
            "authentication_type": "finnish_trust_network",
            "personal_id": "070770-905D",
        },
        {
            "name": "Jakob Doe",
            "email": "jakob.doe@customer.com",
            "role": "signer",
            "authentication_type": "strong_auth_sweden",
        },
    ]
}

response = requests.post("https://sandbox.zefort.com/api/esigns/", 
                         auth=(ZEFORT_APIKEY, ""),
                         json=payload)

print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."

curl -X POST "https://sandbox.zefort.com/api/esigns/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"contract": "ct_1JrjJRTzEMlHVHls", "need_auth_to_view_documents": "true", "parties": [{"name": "John Doe", "email": "john.doe@mycompany.com", "role": "signer", "authentication_type": "sms", "phone_number": "+3581234567"},{"name": "Jane Doe", "email": "jane.doe@customer.com", "role": "signer", "authentication_type": "finnish_trust_network", "personal_id": "070770-905D"},{"name": "Jakob Doe", "email": "jakob.doe@customer.com", "role": "signer", "authentication_type": "strong_auth_sweden"]}'

6. Options for signing order

You can also define the signing order for each signing party. There are three options for sign_order: parallel, consecutive and flexible. Sign order limits how ordinal can be set to signing parties.

Parallel signing order

Parallel sign order is the simplest option. All approvers will first get an approval request. Once all approvers have approved documents, all signers will get a request to sign the contract. At the end, all signers and CC's will receive a link to the final, signed documents.

Consecutive signing order

In consecutive signing order, approvers and signers will approve or sign the contract in a consecutive order. CC's won't have ordinal and will always just receive final documents.

Easiest way to set a signing order is to send parties in one array with desired order. It's also possible to update parties' ordinals with patch request. New parties without ordinal will be set as the last one to sign or approve.

Flexible sign order

In flexible signing order there are no limitations for setting ordinals. When all parties with a given ordinal have completed their signing or approval, parties with the next ordinal will receive an invitation to sign or approve.

If parties are added without ordinal, those will be set as last in the process.

import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
payload = {
    "contract": CONTRACT_ID,
    "sign_order": "consecutive",
    "parties": [
        {
            "name": "John Doe",
            "email": "john.doe@mycompany.com",
            "role": "signer",
            "authentication_type": "email",
            "ordinal": 1,
        },
        {
            "name": "Jack Doe",
            "email": "jack.doe@customer.com",
            "role": "approver",
            "authentication_type": "email",
            "ordinal": 2,
        },
        {
            "name": "Jane Doe",
            "email": "jane.doe@customer.com",
            "role": "signer",
            "authentication_type": "email",
            "ordinal": 3,
        },
    ]
}

response = requests.post("https://sandbox.zefort.com/api/esigns/", 
                         auth=(ZEFORT_APIKEY, ""),
                         json=payload)

print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."

curl -X POST "https://sandbox.zefort.com/api/esigns/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"contract": "ct_1JrjJRTzEMlHVHls", "sign_order": "consecutive", "parties": [{"name": "John Doe", "email": "john.doe@mycompany.com", "role": "signer", "authentication_type": "email", "ordinal": 1},{"name": "Jack Doe", "email": "jack.doe@mycompany.com", "role": "approver", "authentication_type": "email", "ordinal": 2},{"name": "Jane Doe", "email": "jane.doe@customer.com", "role": "signer", "authentication_type": "email", "ordinal": 3}]}'

7. Working with signing requests after activation

Updating signing parties

When the signing request has been activated,signing parties can still be added and existing parties edited and deleted as long as the party has not yet signed or approved the document. When signing is completed, no changes to parties are allowed. Documents cannot be edited after activation.

Here is an example of editing an email address for a signer. In this case, a new signing request will be sent automatically.

import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
PARTY_ID = "sgnr_JrjJRTzEMlHVHls1"

response = requests.patch(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/parties/{PARTY_ID}/", 
    auth=(ZEFORT_APIKEY, ""),
    json={"email": "john.doe@greatcompany.com"}
)

print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID="esgn_lHVHls1JrjJRTzEM"
PARTY_ID="sgnr_JrjJRTzEMlHVHls1"

curl -X PATCH "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/parties/$PARTY_ID/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"email": "john.doe@greatcompany.com"}'

Canceling and reactivating signing request

It is possible to cancel a signing request. When a request is canceled, all parties will be informed via email.

import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"

response = requests.post(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/cancel/", 
    auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"

curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/cancel/" \
     -u $ZEFORT_APIKEY:

An expired or cancelled signing request can be reactivated for signing. If a signing request has expired, the deadline needs to be updated before reactivation.

import datetime
import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
deadline = datetime.datetime.now() + datetime.timedelta(weeks=2)

response = requests.patch(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/", 
    auth=(ZEFORT_APIKEY, ""),
    json={"deadline": str(deadline)}
)

response = requests.post(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/reactivate/", 
    auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"

curl -X PATCH "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"deadline": "2023-12-31 00:00:00"}'

curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/reactivate/" \
     -u $ZEFORT_APIKEY:

Working with completed signing requests

When signing has been completed, the availability of documents can be changed by updating the available_until field. Links to final documents can be resent to all parties who have originally received the final signed documents.

import datetime
import requests

ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
available_until = datetime.datetime.now() + datetime.timedelta(weeks=2)

response = requests.patch(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/", 
    auth=(ZEFORT_APIKEY, ""),
    json={"available_until": str(available_until)}
)

response = requests.post(
    f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/resend_completed/", 
    auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"

curl -X PATCH "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/" \
     -u $ZEFORT_APIKEY: \
     -H "Content-Type: application/json" \
     -d '{"available_until": "2023-12-31 00:00:00"}'

curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/resend_completed/" \
     -u $ZEFORT_APIKEY: