DEV Community

Cover image for Solved: Automating Contract Signing: Typeform to DocuSign Integration
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Automating Contract Signing: Typeform to DocuSign Integration

🚀 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\_name in 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:

  1. 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.
  2. 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.
  3. 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-----"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Pro Tip: In my production setups, I always add Typeform’s webhook signature validation. Typeform sends a Typeform-Signature header 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.

  1. Package your code: Create a deployment package containing your Python script, the config.env file, and all installed libraries.
  2. Deploy to a Serverless Platform: Upload this package to AWS Lambda, Google Cloud Functions, or a similar service.
  3. 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.
  4. 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\_name in 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


Darian Vance

👉 Read the original article on TechResolve.blog


☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)