đ Executive Summary
TL;DR: This guide details how to automate contract signing by integrating Typeform submissions directly with the DocuSign API using a Python script. This solution eliminates manual contract creation and sending, significantly reducing errors and saving operational hours for businesses.
đŻ Key Takeaways
- The core workflow involves Typeform submission triggering a webhook to a Python script, which then calls the DocuSign API to send a pre-templated contract.
- Prerequisites include Typeform (webhooks enabled), DocuSign eSignature (API access, pre-made template with a defined Role like âClientSignerâ), Python 3, and a serverless environment for deployment.
- DocuSign API authentication should use JWT Grant for production, requiring an Integration Key, User ID, Account ID, and an RSA private key.
- The Python script (using Flask and
docusign-esign) acts as the âglue,â parsing Typeform webhook payloads to extract signer name and email, then dynamically creating and sending a DocuSign envelope based on a template. - Securely store API credentials and private keys using environment variables (e.g.,
config.env) and deploy the script to a serverless platform like AWS Lambda or Google Cloud Functions with an API Gateway endpoint. - Crucial pitfalls to avoid include mixing DocuSign demo and production API hosts, ensuring the
role\_namein the Python script precisely matches the DocuSign template role, and correctly parsing the Typeform webhook JSON payload.
Automating Contract Signing: Typeform to DocuSign Integration
Hey everyone, Darian Vance here. Letâs talk about a workflow that genuinely saved our ops team hours of mind-numbing work each week. We used to manually create and send consulting contracts after a lead filled out our Typeform. It was slow, prone to copy-paste errors, and frankly, a waste of good engineering time.
I realized I could connect Typeformâs webhooks directly to the DocuSign API. Now, a client fills out the form, and a perfectly formatted contract lands in their inbox moments later. Itâs a fantastic piece of automation, and I want to walk you through how to build it yourself. Weâre busy people, so letâs focus on getting this done efficiently.
Prerequisites
Before we start, make sure you have the following ready:
- A Typeform account with a plan that supports webhooks.
- A DocuSign eSignature account with API access (this is usually part of their business plans).
- A pre-made DocuSign Template. This is the document you want to send. Crucially, you must define a Role for the signer (e.g., âClientSignerâ).
- Python 3. Weâll be writing the logic in Python.
- An environment to run your code, like a server or a serverless platform (AWS Lambda, Google Cloud Functions, etc.).
The Step-by-Step Guide
Step 1: Understand the Flow
The logic is straightforward. Itâs a classic webhook pattern:
Typeform Submission â Webhook Event Sent â Our Python Script (API Endpoint) â DocuSign API Call â Contract Sent to Signer
Our Python script acts as the âglue,â catching the data from Typeform and reformatting it for DocuSign.
Step 2: Configure DocuSign and Get Your Credentials
This is the most critical setup step. In your DocuSign account:
- Finalize Your Template: Open the template you want to use. Make sure you have a defined Role (e.g., âClientSignerâ). Note this role name down precisely; itâs case-sensitive.
- Get API Credentials: Navigate to your Admin settings, then to âApps and Keysâ. Youâll need to add an app to get an Integration Key (Client ID). You will also need your Account ID and the API User ID.
- Authentication: For production, I strongly recommend using JWT Grant for authentication. This is more secure than legacy methods. Youâll need to generate an RSA key pair and upload the public key to DocuSign. Keep the private key safe!
Store these values securely. Weâll load them as environment variables later.
Step 3: Set Up Your Typeform
Create a simple form. For this example, youâll need at least two fields:
- A âTextâ question for the signerâs full name.
- An âEmailâ question for their email address.
Once your form is ready, go to the âConnectâ tab and find the âWebhooksâ section. Click âAdd a webhookâ. It will ask for an endpoint URL. We donât have this URL yet, but weâll generate it in Step 5 when we deploy our script. For now, you can use a placeholder or a service like webhook.site to inspect the payload.
Step 4: The Python Script â The âGlueâ
Okay, letâs get to the code. Iâll skip the standard virtualenv setup since you likely have your own workflow for that. Letâs jump straight to the Python logic. Youâll need a few libraries, which you can typically install by running: pip install docusign-esign python-dotenv requests in your terminal.
First, create a config.env file to hold your secrets. Never hardcode credentials!
# config.env
DOCUSIGN_INTEGRATION_KEY="YOUR_INTEGRATION_KEY_HERE"
DOCUSIGN_USER_ID="YOUR_USER_ID_HERE"
DOCUSIGN_ACCOUNT_ID="YOUR_ACCOUNT_ID_HERE"
DOCUSIGN_TEMPLATE_ID="YOUR_TEMPLATE_ID_HERE"
# The full content of your private.key file, often on a single line
DOCUSIGN_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----"
Now for the Python script. This code will act as a web server (using Flask here for simplicity) to receive the webhook from Typeform, parse the data, and make the API call to DocuSign.
import os
from flask import Flask, request, jsonify
from docusign_esign import ApiClient, EnvelopesApi, EnvelopeDefinition, TemplateRole, Signer, SignHere, Tabs
from dotenv import load_dotenv
load_dotenv(dotenv_path='config.env')
app = Flask(__name__)
# --- DocuSign API Configuration ---
api_client = ApiClient()
api_client.host = "https://demo.docusign.net/restapi" # Use https://{region}.docusign.net for production
# For JWT Grant authentication
private_key = os.getenv("DOCUSIGN_PRIVATE_KEY").replace('\\n', '\n')
oauth_host = "account-d.docusign.com" # Use account.docusign.com for production
try:
access_token_bytes = api_client.request_jwt_user_token(
client_id=os.getenv("DOCUSIGN_INTEGRATION_KEY"),
user_id=os.getenv("DOCUSIGN_USER_ID"),
oauth_host_name=oauth_host,
private_key_bytes=private_key.encode("utf-8"),
expires_in=3600,
scopes=["signature", "impersonation"]
)
access_token = access_token_bytes.to_dict()["access_token"]
api_client.add_default_header("Authorization", f"Bearer {access_token}")
except Exception as e:
print(f"Error during DocuSign authentication: {e}")
# In a real app, you'd have more robust error handling here.
@app.route('/webhook/typeform', methods=['POST'])
def typeform_webhook():
payload = request.get_json()
try:
# Navigate the Typeform payload to find the answers
answers = payload.get('form_response', {}).get('answers', [])
# NOTE: The logic to find name and email depends on your form structure.
# Inspect your webhook payload to get the correct field IDs or types.
signer_email = next((ans['email'] for ans in answers if ans['type'] == 'email'), None)
signer_name = next((ans['text'] for ans in answers if ans['type'] == 'text'), None)
if not signer_email or not signer_name:
return jsonify({"status": "error", "message": "Name or email not found in payload"}), 400
# Create the DocuSign Envelope from the template
envelope_definition = EnvelopeDefinition(
template_id=os.getenv("DOCUSIGN_TEMPLATE_ID"),
status="sent" # "sent" sends the envelope immediately
)
# Map the signer info from Typeform to the template role
signer = Signer(
email=signer_email,
name=signer_name,
role_name="ClientSigner", # This MUST match the role in your DocuSign template
recipient_id="1",
)
template_role = TemplateRole(
role_name="ClientSigner",
tabs=Tabs(),
**signer.to_dict()
)
envelope_definition.template_roles = [template_role]
# Make the API call
envelopes_api = EnvelopesApi(api_client)
results = envelopes_api.create_envelope(account_id=os.getenv("DOCUSIGN_ACCOUNT_ID"), envelope_definition=envelope_definition)
print(f"Envelope sent! ID: {results.envelope_id}")
return jsonify({"status": "success", "envelope_id": results.envelope_id}), 200
except Exception as e:
print(f"An error occurred: {e}")
return jsonify({"status": "error", "message": "Internal server error"}), 500
if __name__ == '__main__':
app.run(port=5000)
Pro Tip: In my production setups, I always add Typeformâs webhook signature validation. Typeform sends a
Typeform-Signatureheader with each request. You can use your webhookâs secret key to compute a hash of the payload and verify that it matches the one in the header. This ensures the request is genuinely from Typeform and hasnât been tampered with.
Step 5: Deploy the Script
This script needs to be running on a server with a public URL. For something simple like this, a serverless function is perfect and cost-effective.
-
Package your code: Create a deployment package containing your Python script, the
config.envfile, and all installed libraries. - Deploy to a Serverless Platform: Upload this package to AWS Lambda, Google Cloud Functions, or a similar service.
- Create an API Endpoint: Use a service like Amazon API Gateway to create a public HTTPS endpoint that triggers your function. This will give you the final URL.
- Update Typeform: Go back to your Typeform webhook settings and paste this public URL into the field.
Now, test it out! Fill out your Typeform, and within moments, a DocuSign email should appear in the inbox you provided.
Where I Usually Mess Up (Common Pitfalls)
-
Demo vs. Production Endpoints: DocuSign has different API hosts for its developer sandbox (
demo.docusign.net) and production environments. Forgetting to switch this over when going live is a classic mistake. Iâve done it. Twice. -
Role Name Mismatch: The
role\_namein the Python script (âClientSignerâin our example) must be an exact, case-sensitive match to the Role Name in your DocuSign template. - Parsing the Payload Incorrectly: The structure of the Typeform JSON can be nested. I always use a tool to inspect the first webhook payload I receive to confirm the path to the name and email fields before writing my final parsing logic.
Conclusion
And thatâs the core of it. With a single Python script, youâve bridged two powerful SaaS platforms and automated a critical business process. This pattern of using webhooks to trigger serverless functions is incredibly powerful. You can adapt it for all sorts of thingsâcreating Jira tickets from form submissions, adding users to a mailing list, or posting notifications in Slack. By automating these small, repetitive tasks, you free up your team to focus on what really matters.
Happy building!
â Darian
đ Read the original article on TechResolve.blog
â Support my work
If this article helped you, you can buy me a coffee:

Top comments (0)