Skip to main content

Guide

Documenso vs SignWell vs DocuSign SDK 2026

Compare Documenso, SignWell, and DocuSign SDK for electronic signatures. Document signing, templates, webhooks, and which e-signature API to use in 2026.

·PkgPulse Team·
0

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

Electronic signatures are only useful if they are legally valid and auditable. In the United States, the ESIGN Act and UETA establish the legal framework for e-signatures, and all three platforms meet these requirements. DocuSign provides the most comprehensive compliance portfolio: SOC 2 Type II, ISO 27001, HIPAA BAA availability, and eIDAS qualification for EU advanced electronic signatures. For healthcare, financial services, and legal industries where compliance certifications are often contractual requirements with enterprise customers, DocuSign's documentation and compliance team is a significant operational advantage. SignWell holds SOC 2 Type II certification and complies with ESIGN, UETA, and GDPR, which covers most B2B SaaS use cases. Documenso, being newer and open-source, is working toward formal compliance certifications, and self-hosted deployments give organizations direct control over data handling, which can satisfy data residency requirements without a formal certification from a third party.

Audit Trail Quality and Certificate of Completion

The audit trail quality differs substantially across these platforms, and this matters for dispute resolution. DocuSign's Certificate of Completion is the most detailed: it includes a tamper-evident signature chain, timestamps for each recipient action (viewed, signed, declined), IP addresses, and the email addresses of all participants. This certificate is legally recognized evidence in most jurisdictions. SignWell provides a similar audit trail as part of each completed document, accessible via the API and downloadable as a PDF. Documenso generates an audit trail for each signing event stored in its database, and self-hosted deployments retain this data internally, which is important for organizations that cannot share signing metadata with third-party processors. One practical difference: DocuSign's Connect webhook delivers highly detailed event payloads including agent and server metadata that enterprise compliance teams often require for their SIEM ingestion pipelines.

Embedding and White-Labeling for SaaS Products

Teams building e-signature features into SaaS products care deeply about embedded signing UX and white-labeling capabilities. Documenso's embedded signing iframe is fully white-labeled when self-hosted — because you control the domain and application, the signing interface can match your brand perfectly with no visible third-party attribution. This is a meaningful competitive advantage for SaaS products in legal tech, HR tech, or fintech where the signing experience is part of the core product value. SignWell's embedded signing URL injects an iframe hosted on SignWell's domain, and while custom branding (logo, colors) is supported, the base URL remains on SignWell's infrastructure. DocuSign's embedded signing also shows on DocuSign-hosted URLs, and custom domain support is limited to enterprise tiers. For SaaS founders evaluating build-vs-buy, Documenso's self-hosted model makes it the only option that delivers a genuinely white-labeled embedded signing experience without enterprise-tier pricing.

API Design and Developer Experience

The developer experience of integrating e-signature APIs spans from initial setup through webhook handling and document lifecycle management. SignWell has the most developer-friendly REST API design: flat JSON payloads, predictable endpoint structure, clear field naming, and a generous free tier that allows experimentation without upfront commitment. The base64-encoded file upload approach works across all HTTP clients without multipart form handling complexity. DocuSign's SDK (docusign-esign) abstracts the REST API behind an object-oriented interface with explicit class instantiation for every concept (Envelope, Document, Signer, Tabs), which can feel verbose compared to simpler REST API wrappers. Documenso's API follows REST conventions closely and is actively evolving — some features available in the UI don't yet have API coverage, which is a consideration for teams that need programmatic control over all aspects of the signing workflow.

Self-Hosting Documenso in Production

Running Documenso in production requires understanding its technical dependencies beyond the basic Docker setup. Documenso uses Prisma with PostgreSQL for document metadata, and document files are stored either on the local filesystem (not recommended for multi-instance deployments) or in an S3-compatible object store. A production deployment should configure NEXT_PRIVATE_UPLOAD_TRANSPORT=s3 and provide AWS S3 or Cloudflare R2 credentials for reliable document storage with high availability. Document signing certificates require a properly configured certificate passphrase, and the signing keys should be backed up separately from the application database to allow certificate verification of previously signed documents after a database restore. Email delivery for signing requests uses SMTP, and for production reliability, a transactional email provider (Resend, Postmark, SendGrid) is strongly recommended over self-hosted SMTP. The application runs on Next.js 14 with Server Actions, so deployment targets that support Node.js server rendering (Coolify, Railway, Render, or your own VPS) work without modification.

Webhook Reliability and Event-Driven Signing Workflows

Electronic signature workflows are inherently event-driven: a document is sent, a signer views it, each party signs in sequence, and the completed document is delivered to all parties. Building reliable integrations requires careful webhook handling rather than polling the API for status updates. SignWell's webhook payloads follow a consistent schema across all event types, and their webhook delivery includes a retry policy that re-attempts failed deliveries with exponential backoff — if your receiving endpoint returns a non-2xx response, SignWell will retry up to 72 hours, giving your infrastructure time to recover from outages. DocuSign's Connect webhook system is more configurable but also more complex: you specify which envelope events to subscribe to (sent, delivered, completed, declined, voided), and DocuSign delivers events to your HTTPS endpoint with HMAC signature verification. Documenso's webhook support covers the core signing lifecycle events and is actively expanding. For production implementations, storing webhook payloads in a durable queue (SQS, BullMQ) and processing them asynchronously decouples your signing workflow logic from the webhook delivery timing, preventing dropped events when your application is momentarily unavailable during a deployment.


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 →

See also: AVA vs Jest and Medusa vs Saleor vs Vendure 2026, Cal.com vs Calendly vs Nylas (2026).

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.