Skip to main content

Documenso vs SignWell vs DocuSign SDK: E-Signature APIs (2026)

·PkgPulse Team

TL;DR

Documenso is the open-source e-signature platform — self-hosted, signing API, templates, React components, embeddable signing, designed as the open-source DocuSign alternative. SignWell is the developer-friendly e-signature API — simple REST API, templates, bulk sending, embeddable, webhooks, built for developers. DocuSign is the enterprise e-signature platform — eSignature REST API, envelopes, templates, Connect webhooks, PowerForms, the industry standard. In 2026: Documenso for open-source self-hosted signing, SignWell for simple developer-first API, DocuSign for enterprise compliance.

Key Takeaways

  • Documenso: 9K+ GitHub stars — open-source, self-hosted, React components
  • SignWell: Developer-first — simple API, templates, bulk send, embed
  • DocuSign: Industry standard — enterprise, compliance, 1B+ signatures
  • Documenso provides the only fully open-source e-signature solution
  • SignWell offers the simplest API for developers building signing flows
  • DocuSign has the most compliance certifications and enterprise features

Documenso

Documenso — open-source e-signature:

Self-hosted setup

# Docker:
docker run -d \
  --name documenso \
  -p 3000:3000 \
  -e NEXTAUTH_URL=http://localhost:3000 \
  -e NEXTAUTH_SECRET=your-secret \
  -e DATABASE_URL=postgresql://user:pass@db:5432/documenso \
  -e NEXT_PRIVATE_SIGNING_PASSPHRASE=your-passphrase \
  documenso/documenso:latest

API usage

// Documenso API:
const DOCUMENSO_URL = "https://app.documenso.com"
const DOCUMENSO_TOKEN = process.env.DOCUMENSO_API_TOKEN!

const headers = {
  Authorization: `Bearer ${DOCUMENSO_TOKEN}`,
  "Content-Type": "application/json",
}

// Create document from template:
const document = await fetch(`${DOCUMENSO_URL}/api/v1/documents`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    title: "Service Agreement",
    recipients: [
      {
        email: "signer@example.com",
        name: "John Doe",
        role: "SIGNER",
      },
      {
        email: "viewer@example.com",
        name: "Jane Smith",
        role: "VIEWER",
      },
    ],
  }),
}).then((r) => r.json())

// Upload PDF:
const formData = new FormData()
formData.append("file", pdfBuffer, "agreement.pdf")

await fetch(`${DOCUMENSO_URL}/api/v1/documents/${document.id}/upload`, {
  method: "POST",
  headers: { Authorization: `Bearer ${DOCUMENSO_TOKEN}` },
  body: formData,
})

// Add signature fields:
await fetch(`${DOCUMENSO_URL}/api/v1/documents/${document.id}/fields`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    fields: [
      {
        type: "SIGNATURE",
        recipientEmail: "signer@example.com",
        page: 1,
        positionX: 100,
        positionY: 700,
        width: 200,
        height: 50,
      },
      {
        type: "DATE",
        recipientEmail: "signer@example.com",
        page: 1,
        positionX: 350,
        positionY: 700,
        width: 150,
        height: 30,
      },
      {
        type: "NAME",
        recipientEmail: "signer@example.com",
        page: 1,
        positionX: 100,
        positionY: 650,
        width: 200,
        height: 30,
      },
    ],
  }),
})

// Send for signing:
await fetch(`${DOCUMENSO_URL}/api/v1/documents/${document.id}/send`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    message: "Please review and sign this agreement.",
  }),
})

// Check document status:
const status = await fetch(
  `${DOCUMENSO_URL}/api/v1/documents/${document.id}`,
  { headers }
).then((r) => r.json())

console.log(`Status: ${status.status}`) // PENDING, COMPLETED

Templates

// Create template:
const template = await fetch(`${DOCUMENSO_URL}/api/v1/templates`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    title: "NDA Template",
    roles: [
      { name: "Signer", type: "SIGNER" },
      { name: "Company Rep", type: "SIGNER" },
    ],
  }),
}).then((r) => r.json())

// Create document from template:
const doc = await fetch(
  `${DOCUMENSO_URL}/api/v1/templates/${template.id}/use`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      recipients: [
        { role: "Signer", email: "client@example.com", name: "Client" },
        { role: "Company Rep", email: "rep@company.com", name: "Rep" },
      ],
    }),
  }
).then((r) => r.json())

Embeddable signing

// Embed Documenso signing in your app:
function SigningEmbed({ signingUrl }: { signingUrl: string }) {
  return (
    <iframe
      src={signingUrl}
      style={{ width: "100%", height: "80vh", border: "none" }}
      title="Sign Document"
    />
  )
}

// Get signing URL via API:
const signingToken = await fetch(
  `${DOCUMENSO_URL}/api/v1/documents/${docId}/signing-token`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({ recipientEmail: "signer@example.com" }),
  }
).then((r) => r.json())

const signingUrl = `${DOCUMENSO_URL}/sign/${signingToken.token}`

SignWell

SignWell — developer-friendly e-signature:

API setup

const SIGNWELL_API_KEY = process.env.SIGNWELL_API_KEY!

const headers = {
  "X-Api-Key": SIGNWELL_API_KEY,
  "Content-Type": "application/json",
}

Create and send documents

// Create document from file:
const document = await fetch("https://www.signwell.com/api/v1/documents", {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "Service Agreement",
    recipients: [
      {
        id: "signer-1",
        email: "client@example.com",
        name: "John Doe",
        placeholder_name: "Client",
      },
    ],
    files: [
      {
        name: "agreement.pdf",
        file_base64: base64EncodedPdf,
      },
    ],
    fields: [
      [
        {
          type: "signature",
          required: true,
          recipient_id: "signer-1",
          page: 1,
          x: 100,
          y: 700,
          width: 200,
          height: 50,
        },
        {
          type: "date",
          required: true,
          recipient_id: "signer-1",
          page: 1,
          x: 350,
          y: 700,
          width: 150,
          height: 30,
        },
        {
          type: "text",
          required: true,
          recipient_id: "signer-1",
          label: "Full Name",
          page: 1,
          x: 100,
          y: 650,
          width: 200,
          height: 30,
        },
      ],
    ],
    draft: false,  // false = send immediately
    reminders: true,
    apply_signing_order: false,
    custom_requester_name: "PkgPulse",
    custom_requester_email: "signing@pkgpulse.com",
  }),
}).then((r) => r.json())

console.log(`Document ID: ${document.id}`)
console.log(`Status: ${document.status}`)  // pending, completed, expired

Templates

// Create from template:
const fromTemplate = await fetch(
  "https://www.signwell.com/api/v1/document_templates/documents",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      template_id: "template-uuid",
      recipients: [
        {
          placeholder_name: "Client",
          email: "client@example.com",
          name: "John Doe",
        },
        {
          placeholder_name: "Company",
          email: "rep@company.com",
          name: "Company Rep",
        },
      ],
      // Pre-fill template fields:
      template_fields: [
        {
          api_id: "company_name_field",
          value: "PkgPulse Inc.",
        },
        {
          api_id: "contract_date_field",
          value: "2026-03-09",
        },
      ],
      name: "Service Agreement - John Doe",
    }),
  }
).then((r) => r.json())

Bulk sending

// Send document to multiple recipients at once:
const bulk = await fetch(
  "https://www.signwell.com/api/v1/document_templates/bulk_send",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      template_id: "template-uuid",
      bulk_send_list: [
        {
          recipients: [
            { placeholder_name: "Signer", email: "alice@example.com", name: "Alice" },
          ],
          template_fields: [
            { api_id: "name_field", value: "Alice Johnson" },
          ],
        },
        {
          recipients: [
            { placeholder_name: "Signer", email: "bob@example.com", name: "Bob" },
          ],
          template_fields: [
            { api_id: "name_field", value: "Bob Smith" },
          ],
        },
        {
          recipients: [
            { placeholder_name: "Signer", email: "carol@example.com", name: "Carol" },
          ],
          template_fields: [
            { api_id: "name_field", value: "Carol Davis" },
          ],
        },
      ],
    }),
  }
).then((r) => r.json())

Embedded signing and webhooks

// Get embedded signing URL:
const embedded = await fetch(
  `https://www.signwell.com/api/v1/documents/${documentId}/embedded_signing_url`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      recipient_email: "signer@example.com",
      redirect_url: "https://app.example.com/signed",
    }),
  }
).then((r) => r.json())

// Use in iframe or redirect:
const signingUrl = embedded.embedded_signing_url
// Webhook handler:
import express from "express"

const app = express()
app.use(express.json())

app.post("/webhooks/signwell", (req, res) => {
  const { event, data } = req.body

  switch (event) {
    case "document_completed":
      console.log(`Document ${data.document.id} completed!`)
      // Download signed PDF, update database, etc.
      break

    case "document_viewed":
      console.log(`${data.recipient.email} viewed the document`)
      break

    case "recipient_completed":
      console.log(`${data.recipient.email} signed`)
      break

    case "document_expired":
      console.log(`Document ${data.document.id} expired`)
      break
  }

  res.sendStatus(200)
})

DocuSign

DocuSign — enterprise e-signature:

Setup

npm install docusign-esign
// lib/docusign.ts
import docusign from "docusign-esign"

const apiClient = new docusign.ApiClient()
apiClient.setBasePath("https://demo.docusign.net/restapi")  // Use na*.docusign.net for prod

// JWT authentication:
async function getAccessToken() {
  const results = await apiClient.requestJWTUserToken(
    process.env.DOCUSIGN_INTEGRATION_KEY!,
    process.env.DOCUSIGN_USER_ID!,
    ["signature", "impersonation"],
    Buffer.from(process.env.DOCUSIGN_PRIVATE_KEY!, "base64"),
    3600
  )

  apiClient.addDefaultHeader("Authorization", `Bearer ${results.body.access_token}`)
  return results.body.access_token
}

export { apiClient, getAccessToken }

Create and send envelopes

import docusign from "docusign-esign"
import { apiClient, getAccessToken } from "@/lib/docusign"

async function sendForSignature(pdfBase64: string, signer: { email: string; name: string }) {
  await getAccessToken()

  const envelopesApi = new docusign.EnvelopesApi(apiClient)

  // Create envelope:
  const envelope = new docusign.EnvelopeDefinition()
  envelope.emailSubject = "Please sign: Service Agreement"
  envelope.emailBlurb = "Please review and sign the attached agreement."

  // Add document:
  const document = new docusign.Document()
  document.documentBase64 = pdfBase64
  document.name = "Service Agreement"
  document.fileExtension = "pdf"
  document.documentId = "1"
  envelope.documents = [document]

  // Add signer:
  const signerObj = new docusign.Signer()
  signerObj.email = signer.email
  signerObj.name = signer.name
  signerObj.recipientId = "1"
  signerObj.routingOrder = "1"

  // Add signature tab:
  const signHere = new docusign.SignHere()
  signHere.anchorString = "/sig1/"
  signHere.anchorUnits = "pixels"
  signHere.anchorXOffset = "0"
  signHere.anchorYOffset = "0"

  // Add date tab:
  const dateField = new docusign.DateSigned()
  dateField.anchorString = "/date1/"
  dateField.anchorUnits = "pixels"

  const tabs = new docusign.Tabs()
  tabs.signHereTabs = [signHere]
  tabs.dateSignedTabs = [dateField]
  signerObj.tabs = tabs

  const recipients = new docusign.Recipients()
  recipients.signers = [signerObj]
  envelope.recipients = recipients

  envelope.status = "sent"  // Send immediately

  const result = await envelopesApi.createEnvelope(
    process.env.DOCUSIGN_ACCOUNT_ID!,
    { envelopeDefinition: envelope }
  )

  return {
    envelopeId: result.envelopeId,
    status: result.status,
    sentAt: result.statusDateTime,
  }
}

Templates

// Create envelope from template:
async function sendFromTemplate(
  templateId: string,
  signer: { email: string; name: string },
  prefillData: Record<string, string>
) {
  await getAccessToken()
  const envelopesApi = new docusign.EnvelopesApi(apiClient)

  const envelope = new docusign.EnvelopeDefinition()
  envelope.templateId = templateId
  envelope.status = "sent"

  // Template role (maps to template signer):
  const templateRole = new docusign.TemplateRole()
  templateRole.email = signer.email
  templateRole.name = signer.name
  templateRole.roleName = "Signer"

  // Pre-fill tabs:
  const textTabs = Object.entries(prefillData).map(([label, value]) => {
    const tab = new docusign.Text()
    tab.tabLabel = label
    tab.value = value
    return tab
  })

  const tabs = new docusign.Tabs()
  tabs.textTabs = textTabs
  templateRole.tabs = tabs

  envelope.templateRoles = [templateRole]

  return await envelopesApi.createEnvelope(
    process.env.DOCUSIGN_ACCOUNT_ID!,
    { envelopeDefinition: envelope }
  )
}

Embedded signing and webhooks

// Embedded signing (in your app):
async function getEmbeddedSigningUrl(envelopeId: string, signer: { email: string; name: string }) {
  await getAccessToken()
  const envelopesApi = new docusign.EnvelopesApi(apiClient)

  const viewRequest = new docusign.RecipientViewRequest()
  viewRequest.returnUrl = "https://app.example.com/signing-complete"
  viewRequest.authenticationMethod = "none"
  viewRequest.email = signer.email
  viewRequest.userName = signer.name
  viewRequest.recipientId = "1"

  const result = await envelopesApi.createRecipientView(
    process.env.DOCUSIGN_ACCOUNT_ID!,
    envelopeId,
    { recipientViewRequest: viewRequest }
  )

  return result.url  // Redirect user to this URL
}
// DocuSign Connect webhook handler:
import express from "express"
import { XMLParser } from "fast-xml-parser"

const app = express()
app.use(express.text({ type: "text/xml" }))

app.post("/webhooks/docusign", (req, res) => {
  const parser = new XMLParser()
  const data = parser.parse(req.body)
  const envelope = data.DocuSignEnvelopeInformation.EnvelopeStatus

  const status = envelope.Status
  const envelopeId = envelope.EnvelopeID

  switch (status) {
    case "Completed":
      console.log(`Envelope ${envelopeId} completed`)
      // Download signed docs, update records
      break
    case "Sent":
      console.log(`Envelope ${envelopeId} sent`)
      break
    case "Declined":
      console.log(`Envelope ${envelopeId} declined`)
      break
    case "Voided":
      console.log(`Envelope ${envelopeId} voided`)
      break
  }

  res.sendStatus(200)
})

Feature Comparison

FeatureDocumensoSignWellDocuSign
LicenseAGPL v3ProprietaryProprietary
Self-hosted
REST API
Templates
Bulk sending
Embedded signing
Webhooks✅ (Connect)
Anchor tagsBasic
Signing order
SMS delivery
In-person signing
ComplianceBasicSOC 2SOC 2, HIPAA, eIDAS
Mobile signingWeb-basedWeb-based✅ (native app)
Audit trail✅ (detailed)
Branding
Free tier✅ (5 docs/month)✅ (3 docs/month)✅ (trial)
PricingFree / per-seatPer-documentPer-envelope

When to Use Each

Use Documenso if:

  • Want a fully open-source, self-hosted e-signature solution
  • Need to control your signing infrastructure and data
  • Building a product that embeds signing as a core feature
  • Prefer open-source with community governance

Use SignWell if:

  • Want the simplest developer API for adding e-signatures
  • Need bulk sending and template-based document generation
  • Building integrations where signing is a feature, not the product
  • Prefer straightforward REST API without SDK complexity

Use DocuSign if:

  • Need enterprise compliance (HIPAA, eIDAS, SOC 2)
  • Building in regulated industries (healthcare, finance, legal)
  • Want the most feature-rich platform (in-person signing, SMS, PowerForms)
  • Need the industry-standard e-signature that recipients trust

Methodology

GitHub stars and features as of March 2026. Feature comparison based on Documenso v1.x, SignWell API v1, and DocuSign eSignature REST API v2.1.

Compare developer tools and API platforms on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.