Code Examples
Copy-paste integration samples. Get e-signatures working in your stack in minutes.
cURL
The fastest way to test the API. Works on macOS, Linux, and Windows (with Git Bash or WSL).
Send a document for signing
# Encode PDF to base64
PDF_B64=$(base64 -w 0 contract.pdf) # Linux
# PDF_B64=$(base64 -i contract.pdf) # macOS
curl -X POST https://signforge.io/api/v1/quick-sign \
-H "X-API-Key: sf_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Service Agreement\",
\"pdf_base64\": \"$PDF_B64\",
\"signer_email\": \"client@example.com\",
\"signer_name\": \"Jane Doe\"
}"Check envelope status
curl https://signforge.io/api/v1/envelopes/ENVELOPE_ID \
-H "X-API-Key: sf_live_YOUR_KEY"Download signed PDF
curl https://signforge.io/api/v1/envelopes/ENVELOPE_ID/documents/signed \
-H "X-API-Key: sf_live_YOUR_KEY" \
-o signed_contract.pdfList all envelopes
curl "https://signforge.io/api/v1/envelopes?status=completed&limit=20" \
-H "X-API-Key: sf_live_YOUR_KEY"Python
Full workflow: create, send, poll for completion, and download the signed document.
import requests
import base64
import time
API_KEY = "sf_live_YOUR_KEY"
BASE = "https://signforge.io/api/v1"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. Send for signing
with open("contract.pdf", "rb") as f:
pdf_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(f"{BASE}/quick-sign", headers=HEADERS, json={
"title": "Consulting Agreement",
"pdf_base64": pdf_b64,
"signer_email": "client@company.com",
"signer_name": "Alex Chen",
})
data = resp.json()
envelope_id = data["envelope_id"]
print(f"Sent! Envelope: {envelope_id}")
# 2. Poll for completion
while True:
status = requests.get(
f"{BASE}/envelopes/{envelope_id}", headers=HEADERS
).json()
print(f" Status: {status['status']}")
if status["status"] == "completed":
break
time.sleep(30) # Check every 30 seconds
# 3. Download signed PDF
signed = requests.get(
f"{BASE}/envelopes/{envelope_id}/documents/signed",
headers={"X-API-Key": API_KEY},
)
with open("signed_contract.pdf", "wb") as f:
f.write(signed.content)
print("Downloaded signed PDF!")Tip: For production, use webhooks instead of polling. Register a webhook for envelope.completed and you'll get a POST request as soon as the document is signed.
Node.js
Express webhook handler that receives signing events and saves the signed PDF.
const express = require("express");
const crypto = require("crypto");
const fs = require("fs");
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = "your_webhook_secret";
const API_KEY = "sf_live_YOUR_KEY";
const BASE = "https://signforge.io/api/v1";
// Verify webhook signature
function verifySignature(body, signature) {
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(body))
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post("/webhook", async (req, res) => {
const sig = req.headers["x-webhook-signature"];
if (!verifySignature(req.body, sig)) {
return res.status(401).send("Invalid signature");
}
const { event, envelope_id, data } = req.body;
console.log(`Event: ${event} for envelope ${envelope_id}`);
if (event === "envelope.completed") {
// Download the signed PDF
const response = await fetch(
`${BASE}/envelopes/${envelope_id}/documents/signed`,
{ headers: { "X-API-Key": API_KEY } }
);
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync(`signed_${envelope_id}.pdf`, buffer);
console.log("Saved signed PDF!");
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("Webhook server on port 3000"));n8n Workflow
Create an n8n workflow that sends a document for signing when a new row is added to Google Sheets.
Workflow overview
HTTP Request node config
{
"method": "POST",
"url": "https://signforge.io/api/v1/quick-sign",
"headers": {
"X-API-Key": "sf_live_YOUR_KEY",
"Content-Type": "application/json"
},
"body": {
"title": "={{ $json.document_title }}",
"pdf_base64": "={{ $json.pdf_content }}",
"signer_email": "={{ $json.signer_email }}",
"signer_name": "={{ $json.signer_name }}"
}
}Use the “Read Binary File” node before this to read a PDF from Google Drive or an upload, then convert to base64 with an expression.
Retool
Build an internal tool for sending contracts with a Retool form, table, and API queries.
Resource setup
Type: REST API
Base URL: https://signforge.io/api/v1
Headers: X-API-Key: sf_live_YOUR_KEY
Query: Send for signing
// Retool Query — sendForSigning
// Type: POST, Path: /quick-sign
{
"title": {{ textInput_title.value }},
"pdf_base64": {{ fileInput_pdf.value[0].base64 }},
"signer_email": {{ textInput_email.value }},
"signer_name": {{ textInput_name.value }}
}Query: List envelopes
// Retool Query — listEnvelopes
// Type: GET, Path: /envelopes?limit=50
// No body needed — bind the result to a Table componentWhatsApp Bot (Twilio)
Send signing links via WhatsApp using Twilio and the link delivery mode.
from flask import Flask, request
from twilio.rest import Client
import requests
import base64
app = Flask(__name__)
SIGNFORGE_KEY = "sf_live_YOUR_KEY"
BASE = "https://signforge.io/api/v1"
TWILIO_SID = "YOUR_SID"
TWILIO_TOKEN = "YOUR_TOKEN"
TWILIO_FROM = "whatsapp:+14155238886"
twilio = Client(TWILIO_SID, TWILIO_TOKEN)
@app.route("/whatsapp", methods=["POST"])
def handle_message():
body = request.form.get("Body", "").strip()
sender = request.form.get("From", "")
if body.lower().startswith("sign "):
# Parse: "sign Jane jane@example.com"
parts = body.split(" ", 2)
if len(parts) < 3:
reply = "Usage: sign <Name> <email>"
else:
name, email = parts[1], parts[2]
# Create envelope with link delivery (no email)
with open("template_nda.pdf", "rb") as f:
pdf_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(f"{BASE}/quick-sign", json={
"title": "NDA Agreement",
"pdf_base64": pdf_b64,
"signer_email": email,
"signer_name": name,
}, headers={
"X-API-Key": SIGNFORGE_KEY,
"Content-Type": "application/json",
})
data = resp.json()
signing_url = data.get("signing_url", "")
reply = f"NDA sent! Signing link:\n{signing_url}"
# Also send via WhatsApp
twilio.messages.create(
body=f"Hi {name}, please sign your NDA:\n{signing_url}",
from_=TWILIO_FROM,
to=f"whatsapp:{email}", # Replace with phone
)
else:
reply = "Send 'sign <Name> <email>' to create an NDA."
twilio.messages.create(body=reply, from_=TWILIO_FROM, to=sender)
return "OK", 200Airtable Script
Run this script in Airtable's Scripting extension to send documents from your base for signing.
// Airtable Scripting Extension
const API_KEY = "sf_live_YOUR_KEY";
const BASE_URL = "https://signforge.io/api/v1";
// Get the selected record
const table = base.getTable("Contracts");
const record = await input.recordAsync("Select a contract:", table);
if (!record) {
output.text("No record selected.");
} else {
const title = record.getCellValueAsString("Contract Title");
const email = record.getCellValueAsString("Signer Email");
const name = record.getCellValueAsString("Signer Name");
const attachments = record.getCellValue("PDF");
if (!attachments || attachments.length === 0) {
output.text("No PDF attached to this record.");
} else {
// Download the PDF and convert to base64
const pdfUrl = attachments[0].url;
const pdfResp = await fetch(pdfUrl);
const pdfBuffer = await pdfResp.arrayBuffer();
const pdfBytes = new Uint8Array(pdfBuffer);
// Convert to base64
let binary = "";
for (let i = 0; i < pdfBytes.length; i++) {
binary += String.fromCharCode(pdfBytes[i]);
}
const pdfBase64 = btoa(binary);
// Send for signing
const resp = await fetch(BASE_URL + "/quick-sign", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: title,
pdf_base64: pdfBase64,
signer_email: email,
signer_name: name,
}),
});
const data = await resp.json();
// Update the record with envelope ID
await table.updateRecordAsync(record, {
"Envelope ID": data.envelope_id,
"Status": data.status,
});
output.text(
"Sent for signing!\n" +
"Envelope ID: " + data.envelope_id + "\n" +
"Status: " + data.status
);
}
}Batch Shell Script
Send multiple documents from a CSV file. Each row: title, email, name, pdf_path.
#!/bin/bash
# batch_sign.sh — Send documents from CSV
# Usage: ./batch_sign.sh contracts.csv
API_KEY="sf_live_YOUR_KEY"
BASE="https://signforge.io/api/v1"
CSV_FILE="$1"
if [ -z "$CSV_FILE" ]; then
echo "Usage: $0 <csv_file>"
exit 1
fi
# Skip header row, process each line
tail -n +2 "$CSV_FILE" | while IFS=, read -r title email name pdf_path; do
echo "Sending: $title to $email..."
# Encode PDF
PDF_B64=$(base64 -w 0 "$pdf_path" 2>/dev/null || base64 -i "$pdf_path")
# Send via API
RESPONSE=$(curl -s -X POST "$BASE/quick-sign" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"$title\",
\"pdf_base64\": \"$PDF_B64\",
\"signer_email\": \"$email\",
\"signer_name\": \"$name\"
}")
ENVELOPE_ID=$(echo "$RESPONSE" | grep -o '"envelope_id":"[^"]*"' | cut -d'"' -f4)
echo " -> Envelope: $ENVELOPE_ID"
# Rate limit courtesy (1 second between requests)
sleep 1
done
echo "Done! All documents sent."Example CSV
title,email,name,pdf_path
NDA Agreement,alice@company.com,Alice Smith,./docs/nda.pdf
Service Contract,bob@agency.com,Bob Johnson,./docs/service.pdf
Consulting Terms,carol@startup.io,Carol Lee,./docs/consulting.pdfAPI Reference
Full REST API documentation with endpoint reference.
Embed Widget
Embed signing UIs in your app with an iframe and JS SDK.
MCP Server
Use SignForge from Claude Desktop and AI agents.