May 11, 2026 · Tutorial
Python E-Signature API: Sign Documents with 10 Lines of Code
DocuSign's Python SDK pulls in 15+ dependencies and needs 60 lines for a basic send. Signbee needs requests and 10 lines. Here's the complete tutorial — with Flask and FastAPI webhook handlers.
Founder, Signbee
TL;DR
Add e-signatures to any Python app with the standard requests library. Send markdown to Signbee's single endpoint and get back a signing URL. No SDK, no OAuth, no template system. This tutorial covers the basic send, error handling, Flask webhooks, FastAPI webhooks, and a side-by-side comparison with DocuSign's Python SDK.
Prerequisites
The requests library is likely already in your project. If not, it's a single pip install requests. Compare this to DocuSign's Python SDK (docusign-esign), which pulls in 15+ transitive dependencies including urllib3, certifi, pyjwt, and cryptography — adding complexity to your deployment and potential supply chain risk.
Step 1: The basic send (10 lines)
Here's the complete code to send a document for e-signature from Python. Save it as send_nda.py and run it:
import os
import requests
response = requests.post(
"https://signb.ee/api/v1/send",
headers={"Authorization": f"Bearer {os.environ['SIGNBEE_API_KEY']}"},
json={
"markdown": "# Non-Disclosure Agreement\n\n"
"**Disclosing Party:** Acme Corp\n"
"**Receiving Party:** Jane Smith\n\n"
"Confidential information shall be held in strict "
"confidence for a period of 2 years.",
"recipient_name": "Jane Smith",
"recipient_email": "jane@example.com",
},
)
result = response.json()
print(f"Document ID: {result['document_id']}")
print(f"Signing URL: {result['signing_url']}")10 lines (excluding imports and print statements). The API receives your markdown, converts it to a formatted PDF, emails the recipient a signing link, captures their signature, and stores the signed PDF with a full audit trail. You get back a document_id for tracking and a signing_url for embedding the signing flow in your own UI.
Step 2: Production error handling
The basic example works for prototyping. Production code needs to handle HTTP errors, rate limits, and network failures:
import os
import time
import requests
class SignbeeError(Exception):
pass
class RateLimitError(SignbeeError):
def __init__(self, retry_after: int):
self.retry_after = retry_after
super().__init__(f"Rate limited. Retry after {retry_after}s")
def send_for_signature(markdown: str, name: str, email: str) -> dict:
"""Send a document for e-signature. Returns document_id and signing_url."""
response = requests.post(
"https://signb.ee/api/v1/send",
headers={"Authorization": f"Bearer {os.environ['SIGNBEE_API_KEY']}"},
json={
"markdown": markdown,
"recipient_name": name,
"recipient_email": email,
},
timeout=30,
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", "5"))
raise RateLimitError(retry_after)
if not response.ok:
raise SignbeeError(
f"API error {response.status_code}: {response.text}"
)
return response.json()
def send_with_retry(markdown: str, name: str, email: str, max_retries: int = 3) -> dict:
"""Send with automatic retry on rate limits and server errors."""
for attempt in range(max_retries + 1):
try:
return send_for_signature(markdown, name, email)
except RateLimitError as e:
if attempt < max_retries:
time.sleep(e.retry_after)
continue
raise
except requests.ConnectionError:
if attempt < max_retries:
time.sleep(2 ** attempt)
continue
raiseThe wrapper handles three failure modes: rate limits (429 with Retry-After), server errors (5xx with exponential backoff), and network failures (connection errors with retries). For a comprehensive rate limit strategy, see the rate limits guide.
Step 3: Flask webhook handler
To know when a document is signed, viewed, or declined, configure a webhook URL. Here's a minimal Flask implementation:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhooks/signbee", methods=["POST"])
def handle_signbee_webhook():
payload = request.json
event = payload["event"]
data = payload["data"]
if event == "document.signed":
print(f"✅ {data['signer_name']} signed {data['document_id']}")
# Update your database, trigger downstream workflows
# db.documents.update(data["document_id"], status="signed")
elif event == "document.viewed":
print(f"👁️ {data['signer_name']} viewed {data['document_id']}")
elif event == "document.declined":
print(f"❌ {data['signer_name']} declined {data['document_id']}")
# Alert sender, offer to modify and resend
return jsonify({"status": "ok"}), 200
if __name__ == "__main__":
app.run(port=3001)Step 4: FastAPI webhook handler
If your project uses FastAPI (and you should — it's faster, async-native, and has better type safety), here's the equivalent with Pydantic models for payload validation:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Literal, Optional
app = FastAPI()
class WebhookData(BaseModel):
document_id: str
signer_name: str
signer_email: str
signed_pdf_url: Optional[str] = None
ip_address: str
user_agent: str
class WebhookEvent(BaseModel):
event: Literal[
"document.sent", "document.viewed",
"document.signed", "document.declined"
]
timestamp: str
data: WebhookData
@app.post("/webhooks/signbee")
async def handle_webhook(payload: WebhookEvent):
if payload.event == "document.signed":
print(f"Signed: {payload.data.document_id}")
# await db.update_document(payload.data.document_id, "signed")
return {"status": "ok"}FastAPI automatically validates the incoming payload against the Pydantic model. If Signbee sends a malformed event (or someone tries to spoof a webhook), FastAPI returns a 422 before your code even runs. For a complete webhook implementation guide, see the webhooks deep dive.
What the DocuSign Python SDK looks like
For comparison, here's the DocuSign equivalent. This is from their official Python quickstart:
# pip install docusign-esign (15+ transitive dependencies)
from docusign_esign import ApiClient, EnvelopesApi
from docusign_esign.models import (
EnvelopeDefinition, TemplateRole
)
# Step 1: OAuth JWT authentication
api_client = ApiClient()
api_client.set_base_path("https://demo.docusign.net/restapi")
api_client.set_oauth_host_name("account-d.docusign.com")
private_key = open("private.pem", "rb").read()
token = api_client.request_jwt_user_token(
client_id=INTEGRATION_KEY,
user_id=USER_ID,
oauth_host_name="account-d.docusign.com",
private_key_bytes=private_key,
expires_in=3600,
scopes=["signature"],
)
api_client.set_default_header(
"Authorization", f"Bearer {token.access_token}"
)
# Step 2: Create envelope
envelope = EnvelopeDefinition(
email_subject="Please sign this NDA",
template_id=TEMPLATE_ID,
template_roles=[
TemplateRole(
email="jane@example.com",
name="Jane Smith",
role_name="signer",
)
],
status="sent",
)
# Step 3: Send
envelopes_api = EnvelopesApi(api_client)
result = envelopes_api.create_envelope(
account_id=ACCOUNT_ID,
envelope_definition=envelope,
)
# 60+ lines, plus OAuth key management, template pre-configurationIntegration complexity compared
| Factor | DocuSign + SDK | Signbee + requests |
|---|---|---|
| Dependencies | docusign-esign (15+ pkgs) | requests (1 pkg) |
| Authentication | OAuth JWT + private key | API key (Bearer token) |
| Template required? | Yes (pre-configured in UI) | No (markdown in request) |
| Lines for basic send | ~60 lines | 10 lines |
| PDF generation | Upload or use template | Markdown → PDF (server-side) |
| Per-document cost | ~$2.50 | $0.50 |
Batch sending in Python
Need to send documents to 50 signers? Here's an async batch sender using the production wrapper from Step 2:
import csv
from signbee_client import send_with_retry
NDA_TEMPLATE = """# Non-Disclosure Agreement
**Disclosing Party:** Acme Corp
**Receiving Party:** {name}
All confidential information shall remain protected
for a period of 2 years from the date of signing.
"""
def batch_send_ndas(csv_path: str):
results = []
with open(csv_path) as f:
for row in csv.DictReader(f):
try:
result = send_with_retry(
markdown=NDA_TEMPLATE.format(name=row["name"]),
name=row["name"],
email=row["email"],
)
results.append({"status": "sent", **result})
print(f"✅ Sent to {row['name']}")
except Exception as e:
results.append({"status": "failed", "error": str(e)})
print(f"❌ Failed for {row['name']}: {e}")
return results
# Usage: batch_send_ndas("signers.csv")For higher-throughput batch sending with concurrency controls, see the batch sending guide.
Django integration pattern
If your project runs on Django, wrap the Signbee client as a service class and call it from your views:
import os
import requests
class SigningService:
BASE_URL = "https://signb.ee/api/v1"
def __init__(self):
self.api_key = os.environ["SIGNBEE_API_KEY"]
def send_document(self, markdown: str, name: str, email: str) -> dict:
response = requests.post(
f"{self.BASE_URL}/send",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"markdown": markdown,
"recipient_name": name,
"recipient_email": email,
},
timeout=30,
)
response.raise_for_status()
return response.json()
# In your view:
# signing = SigningService()
# result = signing.send_document(markdown, name, email)Frequently Asked Questions
How do I send a document for e-signature from Python?
Use requests.post() to call https://signb.ee/api/v1/send with your API key, markdown content, recipient name, and email. The API handles PDF generation, email delivery, and signature capture. The complete integration is 10 lines.
Do I need DocuSign's Python SDK?
Not with Signbee. DocuSign's SDK is needed because their API requires OAuth JWT auth, private key management, and envelope templates. Signbee uses a Bearer token and a single endpoint — the requests library is all you need. See our full DocuSign comparison.
Can I handle webhooks in Flask or FastAPI?
Yes. Create a POST endpoint that parses the JSON webhook body and returns 200. Signbee sends events for document.sent, document.viewed, document.signed, and document.declined. Both Flask and FastAPI examples are shown above.
Do I need a PDF library for document generation?
No. Signbee converts markdown to a formatted PDF on the server side. You don't need WeasyPrint, ReportLab, or wkhtmltopdf. Send markdown in your API request and the PDF is generated automatically.
10 lines of Python to your first signed document — 5 free docs/month.
Last updated: May 11, 2026 · Michael Beckett is the founder of Signbee and B2bee Ltd.