May 10, 2026 · Tutorial
Add E-Signatures to Node.js in 15 Lines of Code
DocuSign's Node.js SDK is 47MB and needs 80+ lines for a basic send. Signbee needs native fetch() and 15 lines. Here's the complete tutorial — from first API call to production webhook handler.
Founder, Signbee
TL;DR
You don't need an SDK to add e-signatures to Node.js. With Signbee, you write markdown, call one endpoint with native fetch(), and the API handles PDF generation, email delivery, signature capture, and audit trails. This tutorial covers the basic send, production error handling, webhook callbacks, and a comparison to the DocuSign SDK approach.
Prerequisites
You need exactly two things:
No npm install. No package.json additions. No SDK. Node.js 18+ includes fetch() natively, so you can call the Signbee API with zero dependencies. If you're running Node.js 16, you can use node-fetch as a drop-in polyfill.
Step 1: The basic send (15 lines)
Here's the complete code to send a document for e-signature. Save this as send-nda.js and run it with node send-nda.js:
const response = await fetch("https://signb.ee/api/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
},
body: JSON.stringify({
markdown: "# Non-Disclosure Agreement\n\n" +
"**Disclosing Party:** Acme Corp\n" +
"**Receiving Party:** {{recipient_name}}\n\n" +
"The Receiving Party agrees to hold all confidential " +
"information in strict confidence for a period of 2 years.",
recipient_name: "Jane Smith",
recipient_email: "jane@example.com",
}),
});
const { document_id, signing_url } = await response.json();
console.log(`Document sent: ${document_id}`);
console.log(`Signing URL: ${signing_url}`);That's it. 15 lines. The API takes your markdown, converts it to a formatted PDF, emails it to the recipient with a signing link, captures their signature, and stores the signed PDF with a complete audit trail. You get back a document_id for tracking and a signing_url you can embed in your UI if you want inline signing.
Step 2: Production error handling
The basic example works, but production code needs to handle failures gracefully. Here's the pattern I use in every Signbee integration:
async function sendForSignature({ markdown, name, email }) {
const response = await fetch("https://signb.ee/api/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
},
body: JSON.stringify({
markdown,
recipient_name: name,
recipient_email: email,
}),
});
if (!response.ok) {
const error = await response.text();
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || "5";
throw new Error(`Rate limited. Retry after ${retryAfter}s`);
}
throw new Error(`Signbee API error ${response.status}: ${error}`);
}
return response.json();
}
// Usage
try {
const result = await sendForSignature({
markdown: "# Offer Letter\n\nWe're pleased to offer...",
name: "Alex Johnson",
email: "alex@candidate.com",
});
console.log("Sent:", result.document_id);
} catch (err) {
console.error("Failed to send:", err.message);
// Queue for retry, alert ops team, etc.
}The key patterns: check response.ok before parsing JSON, handle 429 rate limits separately (respecting the Retry-After header), and surface the error message from the API body for debugging. For a detailed guide to retry patterns and rate limit handling, see the rate limits guide.
Step 3: Webhook callback handler
Sending is only half the integration. You also need to know when the document is signed, viewed, or declined. Signbee sends webhook events to any URL you configure. Here's a minimal Express.js handler:
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/signbee", async (req, res) => {
const { event, data } = req.body;
switch (event) {
case "document.signed":
console.log(`✅ ${data.signer_name} signed ${data.document_id}`);
// Update your database
await db.documents.update({
where: { id: data.document_id },
data: { status: "signed", signedAt: new Date(data.timestamp) },
});
break;
case "document.viewed":
console.log(`👁️ ${data.signer_name} viewed ${data.document_id}`);
break;
case "document.declined":
console.log(`❌ ${data.signer_name} declined ${data.document_id}`);
// Alert the sender, offer to resend
break;
}
res.status(200).send("OK");
});
app.listen(3001, () => console.log("Webhook handler on :3001"));Configure your webhook URL in the Signbee dashboard or pass it per-document with the webhook_url field. Signbee retries failed deliveries with exponential backoff for up to 72 hours. For a complete webhook implementation guide, see the webhooks deep dive.
What the DocuSign SDK approach looks like
For context, here's what the same "send one document" flow looks like with the DocuSign Node.js SDK. I'm not cherry-picking — this is from their official quickstart guide:
// Step 1: Install SDK (47MB)
// npm install docusign-esign
// Step 2: OAuth configuration
const dsApi = new docusign.ApiClient();
dsApi.setBasePath("https://demo.docusign.net/restapi");
dsApi.addDefaultHeader(
"Authorization", "Bearer " + accessToken // OAuth 2.0 token
);
// Step 3: Create envelope definition
const envDef = new docusign.EnvelopeDefinition();
envDef.emailSubject = "Please sign this NDA";
envDef.templateId = "YOUR_TEMPLATE_ID"; // Pre-configured template
// Step 4: Create template role
const role = docusign.TemplateRole.constructFromObject({
email: "jane@example.com",
name: "Jane Smith",
roleName: "signer",
});
envDef.templateRoles = [role];
envDef.status = "sent";
// Step 5: Send envelope
const envelopesApi = new docusign.EnvelopesApi(dsApi);
const result = await envelopesApi.createEnvelope(
accountId, // Another required parameter
{ envelopeDefinition: envDef }
);
// 80+ lines when you include OAuth token refresh, error handling,
// and template setupThe DocuSign approach requires: an npm package (47MB), an OAuth 2.0 token (which expires and needs refresh logic), a pre-configured envelope template (created in the DocuSign admin UI), an account ID, and roughly 80 lines of code for a production-ready send. For a detailed comparison, see our DocuSign vs Signbee guide.
Integration complexity compared
| Factor | DocuSign + SDK | Signbee + fetch() |
|---|---|---|
| Dependencies | docusign-esign (47MB) | None (native fetch) |
| Authentication | OAuth 2.0 + token refresh | API key (Bearer token) |
| Template required? | Yes (pre-configured in UI) | No (markdown in request) |
| Lines for basic send | ~80 lines | 15 lines |
| Time to first send | 2-4 hours | 30 minutes |
| Per-document cost | ~$2.50 | $0.50 |
Full Express.js integration example
Here's a complete Express.js app that sends documents and handles webhooks — everything you need for a production e-signature integration in under 40 lines:
import express from "express";
const app = express();
app.use(express.json());
// Send a document for signature
app.post("/api/send-contract", async (req, res) => {
const { markdown, recipientName, recipientEmail } = req.body;
const response = await fetch("https://signb.ee/api/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
},
body: JSON.stringify({
markdown,
recipient_name: recipientName,
recipient_email: recipientEmail,
webhook_url: "https://yourapp.com/webhooks/signbee",
}),
});
if (!response.ok) {
return res.status(response.status).json({ error: await response.text() });
}
const data = await response.json();
res.json({ documentId: data.document_id, signingUrl: data.signing_url });
});
// Handle signature events
app.post("/webhooks/signbee", async (req, res) => {
const { event, data } = req.body;
if (event === "document.signed") {
// Your business logic here
console.log(`Signed: ${data.document_id} by ${data.signer_name}`);
}
res.sendStatus(200);
});
app.listen(3000);Using TypeScript
If your project uses TypeScript, define a simple type for the API response and webhook payload:
interface SignbeeResponse {
document_id: string;
signing_url: string;
status: "sent" | "viewed" | "signed" | "declined";
}
interface SignbeeWebhookEvent {
event: "document.sent" | "document.viewed" |
"document.signed" | "document.declined";
timestamp: string;
data: {
document_id: string;
signer_name: string;
signer_email: string;
signed_pdf_url?: string;
ip_address: string;
user_agent: string;
};
}No need for generated types from an OpenAPI spec. The Signbee API surface is small enough that two interfaces cover the entire integration. For the full API reference, see the documentation.
Next steps
Once you have the basic send-and-receive working, explore these related patterns:
Frequently Asked Questions
Do I need an SDK to add e-signatures to Node.js?
No. Signbee's API is a single REST endpoint that works with native fetch() in Node.js 18+. No SDK, no npm packages, no OAuth. The complete integration is 15 lines of JavaScript.
How does this compare to DocuSign's Node.js SDK?
DocuSign's SDK is 47MB, requires OAuth 2.0 token management, pre-configured templates, and ~80 lines of code for a basic send. Signbee uses an API key and 15 lines of native fetch(). See our full comparison.
How do I know when a document is signed?
Configure a webhook URL and Signbee will POST events to it when documents are sent, viewed, signed, or declined. Set up an Express.js route to handle these events. See our webhook guide for the full implementation.
15 lines to your first signed document — 5 free docs/month, no SDK required.
Last updated: May 10, 2026 · Michael Beckett is the founder of Signbee and B2bee Ltd.