E-Signature API Integration: Best Practices & Code Examples
Learn how to integrate e-signature workflows into your application. Complete guide with REST API examples, webhooks, error handling, and security best practices.
David Park
Senior Developer
E-Signature API Integration: Best Practices & Code Examples
Integrating e-signatures into your application shouldn't be complicated. Whether you're building a contract management system, HR portal, or custom business application, this guide shows you how to implement bulletproof e-signature workflows.
API Architecture Overview
RESTful Design Principles
Space Sign's API follows REST best practices:
Base URL: `https://api.spacesign.com/v1`
Authentication: Bearer tokens
```http
Authorization: Bearer YOUR_API_KEY
```
Response Format: JSON
```json
{
"status": "success",
"data": { ... },
"meta": { ... }
}
```
Core Integration Workflows
1. Send a Document for Signature
Basic Flow:
1. Upload document
2. Define signers
3. Place signature fields
4. Send for signing
Code Example (Node.js):
```javascript
const SpaceSign = require('@spacesign/sdk');
const client = new SpaceSign({ apiKey: process.env.SPACESIGN_API_KEY });
async function sendContract() {
try {
// Step 1: Upload document
const document = await client.documents.upload({
file: './contract.pdf',
title: 'Employment Agreement - John Doe'
});
// Step 2: Add signers
const envelope = await client.envelopes.create({
documentId: document.id,
signers: [
{
email: 'john.doe@example.com',
name: 'John Doe',
role: 'Employee',
order: 1
},
{
email: 'hr@company.com',
name: 'Jane Smith',
role: 'HR Manager',
order: 2
}
]
});
// Step 3: Add signature fields (or use AI auto-tag)
await client.envelopes.addFields(envelope.id, {
fields: [
{
type: 'signature',
signer: 'john.doe@example.com',
page: 1,
x: 100,
y: 500,
width: 200,
height: 50
},
{
type: 'date',
signer: 'john.doe@example.com',
page: 1,
x: 100,
y: 550
}
]
});
// Step 4: Send envelope
const sent = await client.envelopes.send(envelope.id, {
message: 'Please review and sign your employment agreement.',
subject: 'Action Required: Employment Agreement'
});
console.log(`Envelope sent! ID: ${sent.id}`);
return sent;
} catch (error) {
console.error('Error sending contract:', error);
throw error;
}
}
```
2. AI-Powered Auto-Tagging
Skip manual field placement - let AI do it:
```javascript
async function sendWithAutoTag() {
const document = await client.documents.upload({
file: './contract.pdf',
title: 'Sales Agreement'
});
// AI automatically detects signature locations
const analysis = await client.ai.analyzeDocument(document.id);
const envelope = await client.envelopes.create({
documentId: document.id,
signers: [{ email: 'client@example.com', name: 'Client Name' }],
autoTag: true // Magic happens here
});
await client.envelopes.send(envelope.id);
}
```
3. Embedded Signing
Integrate signing directly into your app:
```javascript
async function getSigningURL() {
const envelope = await client.envelopes.create({ / ... / });
// Generate embedded signing URL
const signingSession = await client.envelopes.createSigningSession(
envelope.id,
{
signerEmail: 'john.doe@example.com',
returnUrl: 'https://yourapp.com/signing-complete',
expiresIn: 3600 // 1 hour
}
);
return signingSession.url;
// Redirect user to this URL or embed in iframe
}
```
Frontend Implementation:
```html
<iframe
src="{{signingUrl}}"
width="100%"
height="600"
allow="camera; microphone"
></iframe>
```
Webhook Event Handling
Setting Up Webhooks
Register webhook endpoint:
```javascript
await client.webhooks.create({
url: 'https://yourapp.com/webhooks/spacesign',
events: [
'envelope.sent',
'envelope.delivered',
'envelope.signed',
'envelope.completed',
'envelope.declined',
'envelope.expired'
],
secret: 'your-webhook-secret'
});
```
Webhook Handler (Express.js)
```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Verify webhook signature
function verifyWebhookSignature(payload, signature, secret) {
const hash = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(hash)
);
}
app.post('/webhooks/spacesign', async (req, res) => {
const signature = req.headers['x-spacesign-signature'];
// Verify authenticity
if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
switch (event) {
case 'envelope.completed':
await handleEnvelopeCompleted(data);
break;
case 'envelope.signed':
await handleEnvelopeSigned(data);
break;
case 'envelope.declined':
await handleEnvelopeDeclined(data);
break;
}
res.status(200).send('OK');
});
async function handleEnvelopeCompleted(data) {
console.log(`Envelope ${data.envelopeId} completed!`);
// Download signed document
const pdf = await client.envelopes.downloadDocument(data.envelopeId);
// Update your database
await db.contracts.update({
id: data.metadata.contractId,
status: 'signed',
signedDocumentUrl: pdf.url
});
// Trigger next workflow
await triggerOnboarding(data.metadata.employeeId);
}
```
Error Handling Best Practices
Implement Retry Logic
```javascript
async function sendWithRetry(envelope, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await client.envelopes.send(envelope.id);
} catch (error) {
if (error.code === 'RATE_LIMIT_EXCEEDED') {
// Exponential backoff
await sleep(Math.pow(2, i) 1000);
continue;
}
if (error.code === 'VALIDATION_ERROR') {
// Don't retry validation errors
throw error;
}
if (i === maxRetries - 1) throw error;
}
}
}
```
Handle Common Errors
```javascript
try {
await client.envelopes.send(envelopeId);
} catch (error) {
switch (error.code) {
case 'INVALID_EMAIL':
return { error: 'Please check signer email address' };
case 'DOCUMENT_NOT_FOUND':
return { error: 'Document has been deleted' };
case 'INSUFFICIENT_CREDITS':
return { error: 'Please upgrade your plan' };
case 'RATE_LIMIT_EXCEEDED':
return { error: 'Too many requests. Try again in a minute' };
default:
// Log unexpected errors
logger.error('Unexpected API error:', error);
return { error: 'Something went wrong. Please try again' };
}
}
```
Security Best Practices
1. Protect API Keys
NEVER commit API keys to version control:
```javascript
// β BAD
const client = new SpaceSign({
apiKey: 'sk_live_abc123...'
});
// β GOOD
const client = new SpaceSign({
apiKey: process.env.SPACESIGN_API_KEY
});
```
Use different keys for environments:
2. Implement Rate Limiting
```javascript
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 60 1000, // 1 minute
max: 60, // 60 requests per minute
message: 'Too many requests from this IP'
});
app.use('/api/signatures', apiLimiter);
```
3. Validate Webhook Signatures
Always verify webhooks come from Space Sign:
```javascript
// See webhook handler example above
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Unauthorized');
}
```
4. Use HTTPS Only
```javascript
// Enforce HTTPS in production
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect('https://' + req.headers.host + req.url);
}
next();
});
}
```
Performance Optimization
1. Batch Operations
Instead of sending documents one-by-one:
```javascript
// β Slow (sequential)
for (const employee of employees) {
await sendContract(employee);
}
// β Fast (parallel)
await Promise.all(
employees.map(employee => sendContract(employee))
);
```
2. Cache Frequently Accessed Data
```javascript
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes
async function getTemplate(id) {
const cached = cache.get(`template:${id}`);
if (cached) return cached;
const template = await client.templates.get(id);
cache.set(`template:${id}`, template);
return template;
}
```
3. Use Webhooks Instead of Polling
```javascript
// β BAD: Poll for status
setInterval(async () => {
const status = await client.envelopes.getStatus(envelopeId);
if (status === 'completed') {
// do something
}
}, 5000);
// β GOOD: Use webhooks
// Webhook automatically notifies when completed
```
Testing Your Integration
Unit Tests
```javascript
const nock = require('nock');
describe('SpaceSign Integration', () => {
it('should send envelope successfully', async () => {
// Mock API response
nock('https://api.spacesign.com')
.post('/v1/envelopes')
.reply(200, {
status: 'success',
data: { id: 'env_123', status: 'sent' }
});
const result = await sendContract();
expect(result.status).toBe('sent');
});
it('should handle rate limit errors', async () => {
nock('https://api.spacesign.com')
.post('/v1/envelopes')
.reply(429, { error: 'Rate limit exceeded' });
await expect(sendContract()).rejects.toThrow('Rate limit');
});
});
```
Integration Tests
Use test mode API keys for safe testing:
```javascript
const client = new SpaceSign({
apiKey: process.env.SPACESIGN_TEST_KEY,
testMode: true
});
```
Monitoring & Logging
Track API Usage
```javascript
const client = new SpaceSign({
apiKey: process.env.SPACESIGN_API_KEY,
onRequest: (config) => {
logger.info('API Request:', {
method: config.method,
url: config.url,
timestamp: new Date()
});
},
onResponse: (response) => {
logger.info('API Response:', {
status: response.status,
duration: response.duration
});
}
});
```
Error Monitoring
Integrate with Sentry or similar:
```javascript
const Sentry = require('@sentry/node');
client.on('error', (error) => {
Sentry.captureException(error, {
tags: {
integration: 'spacesign',
endpoint: error.endpoint
}
});
});
```
Conclusion
Building robust e-signature integrations requires:
β Proper error handling
β Webhook implementation
β Security best practices
β Performance optimization
β Comprehensive testing
Follow these patterns, and you'll have a production-ready integration that scales.
Need help with your integration? [Join our developer community](https://github.com/pmspaceai7-wq/space-sign/discussions) or [request technical support](/request-a-demo).
Ready to Try Space Sign?
Experience the power of open-source, AI-powered e-signatures.