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.

Michael Beckett
Michael Beckett

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:

Node.js version18+ (for native fetch)
Signbee API keyFree at signb.ee/docs
DependenciesNone (zero npm packages)

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:

send-nda.js — complete Node.js e-signature integration
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:

send-document.js — with error handling
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:

webhook-handler.js — Express.js
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:

DocuSign Node.js SDK — send one document (simplified)
// 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 setup

The 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

FactorDocuSign + SDKSignbee + fetch()
Dependenciesdocusign-esign (47MB)None (native fetch)
AuthenticationOAuth 2.0 + token refreshAPI key (Bearer token)
Template required?Yes (pre-configured in UI)No (markdown in request)
Lines for basic send~80 lines15 lines
Time to first send2-4 hours30 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:

server.js — complete Express.js integration
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:

types.ts — optional TypeScript definitions
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.

Related resources