How to Add Electronic Signatures to Your App via API
A developer's guide to integrating e-signature functionality using the SigPen API. Upload documents, send for signature, embed signing in your UI, and handle webhooks.

If your app generates contracts, agreements, or any document that needs a signature, building the signing flow yourself is a bad use of time. Between handling PDF manipulation, signer authentication, audit trails, and compliance requirements, you're looking at weeks of work for functionality that isn't your product's core value.
The better path is an e-signature API. You pass it a document, it handles the signing flow, and you get a webhook when everything's done.
Here's how to integrate SigPen's API into your application.
What You Need to Start
- A free SigPen Developer account - sign up here (no credit card required)
- An API key from your Developer Dashboard
- A backend that can make HTTP requests and receive webhooks
API access is available on every plan, starting with the free Developer tier. You get 200 requests per day and 10 per minute to start, which is plenty for testing and low-volume integrations.
Authentication
Every API request uses Bearer token authentication:
Authorization: Bearer sp_live_your_api_key_here
You can also pass the key as an X-API-Key header if that fits your setup better.
Your API key is only shown once when you create it, so copy it somewhere safe. SigPen stores a SHA-256 hash, not the key itself, so there's no way to retrieve it later. If you lose it, generate a new one.
For development, use a test-mode key (prefixed sp_test_). Test documents are watermarked, auto-deleted after 14 days, and don't count toward your monthly limit.
The Basic Flow
The core integration is five steps: upload a document, optionally run AI field detection, send for signature, receive a webhook when it's done, then download the signed PDF. Most of them are a single HTTP request.
Step 1: Upload a Document
Send your PDF as a base64-encoded string in a JSON request body:
const fs = require('fs');
const res = await fetch('https://www.sigpen.com/api/v1/documents', {
method: 'POST',
headers: {
'Authorization': 'Bearer sp_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
file: fs.readFileSync('contract.pdf').toString('base64'),
file_name: 'contract.pdf',
title: 'Service Agreement - Acme Corp',
metadata: { deal_id: '42', client: 'acme' },
}),
});
const doc = await res.json();
{
"id": "cm9abc123...",
"title": "Service Agreement - Acme Corp",
"file_name": "contract.pdf",
"status": "draft",
"metadata": { "deal_id": "42", "client": "acme" },
"created_at": "2026-03-07T14:00:00.000Z",
"updated_at": "2026-03-07T14:00:00.000Z"
}
Hold onto that id because you'll use it for everything that follows. The metadata field lets you attach up to 10 key-value pairs for your own reference (deal IDs, customer IDs, internal tags).
Max file size is 25 MB. The API validates that the file is a real PDF before accepting it.
Step 2: AI Field Detection (Optional)
If you want the system to automatically place signature, date, and text fields based on the document's content, you can use AI field detection from the SigPen web editor. This is particularly useful for documents with varying layouts.
Skip this step if your documents have a fixed, known structure. You likely already know exactly where fields should go.
Step 3: Send for Signature
const res = await fetch(`https://www.sigpen.com/api/v1/documents/${doc.id}/send`, {
method: 'POST',
headers: {
'Authorization': 'Bearer sp_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
signers: [
{ email: 'jane@example.com', name: 'Jane Smith', order: 1 },
{ email: 'bob@example.com', name: 'Bob Lee', order: 2 },
],
message: 'Please review and sign the attached service agreement.',
expires_in_days: 30,
}),
});
const sent = await res.json();
{
"id": "cm9abc123...",
"status": "pending",
"signers": [
{
"id": "cm9s1...",
"email": "jane@example.com",
"name": "Jane Smith",
"status": "pending",
"order": 1,
"signed_at": null,
"viewed_at": null
},
{
"id": "cm9s2...",
"email": "bob@example.com",
"name": "Bob Lee",
"status": "waiting",
"order": 2,
"signed_at": null,
"viewed_at": null
}
],
"expires_at": "2026-04-06T14:00:00.000Z",
"sent_at": "2026-03-07T14:00:00.000Z"
}
Each signer gets an email with their unique signing link. They don't need a SigPen account. They just click the link and sign.
For sequential signing, set order: 1, order: 2, etc. SigPen sends each invitation only after the previous signer completes. Useful for contracts where a counter-signature is required. Note the second signer's status is "waiting" until signer 1 finishes.
Save the signer id values. You'll need them if you want to send reminders or generate embedded signing URLs.
Step 4: Receive Webhooks
Register an endpoint to get notified when things happen:
const res = await fetch('https://www.sigpen.com/api/v1/webhooks', {
method: 'POST',
headers: {
'Authorization': 'Bearer sp_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://yourapp.com/webhooks/sigpen',
events: ['document.completed', 'document.signed', 'document.voided'],
}),
});
const webhook = await res.json();
// IMPORTANT: Save webhook.secret - it's only shown once!
When a document is fully signed, you'll receive a POST to your endpoint. The available events are:
| Event | Fires when |
|---|---|
document.sent | Document is sent for signing |
document.viewed | A signer opens the signing page |
document.signed | A signer completes their signature |
document.completed | All signers have signed |
document.declined | A signer declines to sign |
document.voided | The sender voids the document |
document.expired | The signing deadline passes |
Always verify the webhook signature before processing. SigPen signs every delivery with HMAC-SHA256 using your webhook secret:
const crypto = require('crypto');
function verifyWebhook(rawBody, signature, timestamp, secret) {
const payload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expected}` === signature;
}
// In your Express handler:
app.post('/webhooks/sigpen', (req, res) => {
const sig = req.headers['x-sigpen-signature'];
const ts = req.headers['x-sigpen-timestamp'];
if (!verifyWebhook(JSON.stringify(req.body), sig, ts, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the event...
res.sendStatus(200);
});
Step 5: Download the Signed PDF
const res = await fetch(`https://www.sigpen.com/api/v1/documents/${doc.id}/download`, {
headers: { 'Authorization': 'Bearer sp_live_YOUR_KEY' },
});
const buffer = await res.arrayBuffer();
fs.writeFileSync('signed-contract.pdf', Buffer.from(buffer));
This returns the signed PDF binary. Only available once all signers have completed (status is "completed"). Store it wherever makes sense for your application.
Bonus: Embed Signing in Your App
If you want the signing experience to happen inside your own UI instead of redirecting users to SigPen, you can use embedded signing. Generate a short-lived URL and load it in an iframe:
// Get the embedded signing URL
const res = await fetch(
`https://www.sigpen.com/api/v1/documents/${doc.id}/embedded-sign-url`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer sp_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ signer_id: signerId }),
}
);
const { sign_url } = await res.json();
// Load it in an iframe
document.getElementById('signing-frame').src = sign_url;
// Listen for completion
window.addEventListener('message', (event) => {
if (event.data?.source === 'sigpen') {
if (event.data.event === 'sigpen.signed') {
console.log('Document signed!', event.data.documentId);
}
}
});
The iframe fires postMessage events: sigpen.ready when the signing page loads, sigpen.signed when the signer completes, and sigpen.error if something goes wrong. The URL expires after 60 minutes.
Try it yourself in the interactive Embed Sandbox, or read the full Embed documentation.
Error Handling
All errors return a consistent JSON structure:
{
"error": {
"type": "validation_error",
"message": "The 'reason' field is required when voiding a document.",
"code": "VOID_REASON_REQUIRED",
"doc_url": "https://docs.sigpen.com/errors#VOID_REASON_REQUIRED"
}
}
The status codes you'll run into:
| Code | Meaning |
|---|---|
| 400 | Invalid request - missing field, wrong format |
| 401 | Bad or missing API key |
| 403 | Feature not available on your plan |
| 404 | Document doesn't exist |
| 409 | Document is in the wrong state for this action |
| 422 | Request body failed validation |
| 429 | Rate limit hit - check the Retry-After header |
For 429s, implement exponential backoff. Don't retry immediately in a loop.
Getting Started
API access is free. Sign up for a Developer account and you'll have an API key in under a minute. No credit card, no trial expiration.
For higher rate limits and more features, paid plans start at $9/month. See the full API Reference for detailed endpoint documentation with examples in cURL, JavaScript, Python, and PHP.
Further reading: Best E-Signature Software for Small Businesses and How AI Places Signature Fields Automatically.
Ready to simplify your document signing?
Start your 14-day free trial. No credit card required.