Practical Implementation TutorialUpdated September 2026 · 11 min read

How to Add E-Signatures to Any Web App in 10 Minutes

Integrating document signing into your web application shouldn't require wrestling with enterprise OAuth ceremonies, calculating PDF coordinate pixels, or maintaining heavyweight client SDKs. Here is a production tutorial showing how to dispatch legally binding agreements from Next.js 15, Node.js, and Python using a single REST endpoint.

Michael Beckett
Michael Beckett

Founder, Signbee

10 min

Total Setup

1 Call

REST Endpoint

0 KB

SDK Footprint

SHA-256

Audit Seal

Architectural TL;DR

Add legally binding e-signatures to any web application in 10 minutes. No SDK, no OAuth, no coordinate drag-and-drop. A single POST /api/v1/send converts dynamic Markdown into a styled PDF, delivers signing invitations, captures intent in any browser, and notifies your backend via authenticated webhooks. Free tier includes 5 docs/month.

The 10-Minute Integration Flowchart

Compare the friction-free Signbee path with the legacy multi-week enterprise integration:

Integration Lifecycle Comparison
[Signbee Path: 10 Minutes]
1. Grab API Key ──> 2. POST /api/v1/send ──> 3. Recipient Signs ──> 4. Webhook document.signed

[Legacy Vendor Path: 2 to 4 Weeks]
1. Sales Call ──> 2. Sandbox Setup ──> 3. RSA Keypair ──> 4. Admin Consent Redirect ──>
5. Install 15MB SDK ──> 6. Create Draft Envelope ──> 7. Base64 Upload ──> 8. Calculate Tab X/Y ──>
9. Dispatch Envelope ──> 10. Polling / Connect Config

Option 1: Next.js 15 App Router & Server Actions

Modern Next.js applications leverage Server Actions for zero-client bundle overhead and end-to-end type safety. Here is the complete implementation:

actions/sendAgreement.ts (Server Action)
"use server";

interface SendAgreementParams {
  clientName: string;
  clientEmail: string;
  projectScope: string;
  monthlyRetainer: number;
}

export async function sendAgreementAction(params: SendAgreementParams) {
  const currentDate = new Date().toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
  });

  const markdownContent = `# Professional Services Agreement

**Effective Date:** ${currentDate}

## Parties
- **Service Provider:** Acme Web Innovations LLC ("Provider")
- **Client:** ${params.clientName} ("Client")

## Scope of Services
${params.projectScope}

## Commercial Terms
- Monthly Retainer: $${params.monthlyRetainer.toLocaleString()} USD
- Invoicing: Net 15 days via ACH or Credit Card
- Termination: 30 days written notice

By signing below, the parties agree to all legal terms.`;

  const response = await fetch("https://signb.ee/api/v1/send", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      markdown: markdownContent,
      sender_name: "Acme Web Operations",
      sender_email: "contracts@acmeweb.io",
      recipient_name: params.clientName,
      recipient_email: params.clientEmail,
      webhook_url: "https://acmeweb.io/api/webhooks/signbee",
      expires_in_days: 7,
    }),
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`Contract dispatch failed: ${err}`);
  }

  return await response.json();
}

Interactive Client Component with Optimistic Feedback

components/AgreementButton.tsx (Client Component)
"use client";

import { useState } from "react";
import { sendAgreementAction } from "@/actions/sendAgreement";

export function AgreementButton({ clientName, clientEmail }: { clientName: string; clientEmail: string }) {
  const [loading, setLoading] = useState(false);
  const [documentId, setDocumentId] = useState<string | null>(null);

  async function handleDispatch() {
    setLoading(true);
    try {
      const result = await sendAgreementAction({
        clientName,
        clientEmail,
        projectScope: "Full-stack SaaS web development and automated CI/CD pipeline maintenance.",
        monthlyRetainer: 4500,
      });
      setDocumentId(result.document_id);
    } catch (e) {
      alert("Failed to send agreement");
    } finally {
      setLoading(false);
    }
  }

  if (documentId) {
    return (
      <div className="p-4 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm">
        ✅ Agreement successfully dispatched! Tracking ID: <span className="font-mono">{documentId}</span>
      </div>
    );
  }

  return (
    <button
      onClick={handleDispatch}
      disabled={loading}
      className="px-5 py-2.5 rounded-lg bg-emerald-500 hover:bg-emerald-600 text-white font-medium text-sm transition-colors disabled:opacity-50"
    >
      {loading ? "Dispatching Agreement..." : "Send Service Agreement"}
    </button>
  );
}

Option 2: Python / FastAPI Backend Integration

For Python services, dispatching agreements takes fewer than 15 lines of code using httpx:

main.py (FastAPI Endpoint)
from fastapi import FastAPI, HTTPException
import httpx
import os

app = FastAPI()

@app.post("/contracts/dispatch")
async def dispatch_contract(client_name: str, client_email: str):
    api_key = os.getenv("SIGNBEE_API_KEY")
    if not api_key:
        raise HTTPException(status_code=500, detail="Missing SIGNBEE_API_KEY")

    payload = {
        "markdown": f"# Consulting Terms\n\nBetween Acme AI and {client_name}...",
        "sender_name": "Acme AI Corp",
        "sender_email": "contracts@acme-ai.io",
        "recipient_name": client_name,
        "recipient_email": client_email,
        "webhook_url": "https://api.acme-ai.io/webhooks/signbee",
        "expires_in_days": 10
    }

    async with httpx.AsyncClient() as client:
        res = await client.post(
            "https://signb.ee/api/v1/send",
            headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
            json=payload,
            timeout=10.0
        )
        if res.status_code != 200:
            raise HTTPException(status_code=res.status_code, detail=res.text)
        return res.json()

Option 3: Sending Existing Pre-Rendered PDF Files

If your application already generates PDF invoices, tax documents, or blueprints, you can pass existing PDF bytes directly:

Dispatch Existing PDF
const response = await fetch("https://signb.ee/api/v1/send", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    pdf_base64: fileBuffer.toString("base64"),
    sender_name: "Operations Team",
    sender_email: "ops@company.com",
    recipient_name: "Counterparty Executive",
    recipient_email: "exec@counterparty.io",
    webhook_url: "https://company.com/api/webhooks/signbee",
  }),
});

Effort & Complexity Comparison Across Major APIs

Integration StepDocuSign APIDropbox Sign (HelloSign)Signbee API
Auth SetupOAuth 2.0 JWT (RSA Keypair)API KeyAPI Key / Email OTP
Lines of Code65 – 120 lines40 – 60 lines12 – 18 lines
SDK DependencyRequired (15MB package)RecommendedZero (Standard fetch)
Document SourcePDF bytes onlyPDF bytes onlyMarkdown or PDF bytes
Time to First Signature1 – 3 days2 – 4 hours< 10 minutes

Full-Stack Security Architecture: Protecting Secrets in Next.js & Nuxt

When adding electronic signature triggers to modern web applications, the golden security rule is never expose your Signbee API key to client-side JavaScript bundles.

In Next.js App Router or Nuxt 3, create a protected backend Route Handler that validates user session cookies (via Supabase Auth, Clerk, or NextAuth) before proxying the contract dispatch to Signbee:

Next.js App Router — app/api/contracts/route.ts
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth"; // Your authentication provider

export async function POST(req: NextRequest) {
  // 1. Authenticate user session
  const session = await auth();
  if (!session?.user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { dealTitle, counterpartyEmail, counterpartyName, amount } = await req.json();

  // 2. Interpolate dynamic contract terms into Markdown
  const markdownContract = `
# Professional Services Agreement

**Effective Date:** ${new Date().toISOString().split("T")[0]}
**Provider:** Acme Solutions Inc.
**Client:** ${counterpartyName} (${counterpartyEmail})
**Contract Value:** $${amount.toLocaleString()} USD

## Scope of Deliverables
The Provider will deliver software development and technical consulting services as specified.

## Payment & Invoicing Terms
Invoices are payable within 30 days of receipt via electronic bank transfer.

## Cryptographic Acknowledgement
By signing below, both parties confirm their intention to be legally bound under the ESIGN Act and eIDAS.
`;

  // 3. Dispatch to Signbee using server-only environment variable
  const signbeeRes = await fetch("https://signb.ee/api/v1/send", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      markdown: markdownContract,
      recipient_name: counterpartyName,
      recipient_email: counterpartyEmail,
      webhook_url: `${process.env.APP_URL}/api/webhooks/signbee`,
      metadata: {
        userId: session.user.id,
        contractType: "services_agreement",
      },
    }),
  });

  if (!signbeeRes.ok) {
    const err = await signbeeRes.text();
    return NextResponse.json({ error: err }, { status: signbeeRes.status });
  }

  const data = await signbeeRes.json();
  return NextResponse.json({ success: true, documentId: data.document_id });
}

This pattern gives you complete control over authentication, rate limiting, and database audits while keeping your frontend code completely free of sensitive API tokens or complex signature rendering libraries.

Client Error Recovery & Offline States

In modern single-page applications, network interruptions or client connectivity loss must not result in unhandled promise rejections. Wrap your frontend dispatch actions in resilient UI boundaries: display inline non-blocking toast alerts (using Sonner or React Hot Toast), preserve form inputs in local component state if an error occurs, and provide an explicit "Try Again" button that re-submits the verified payload.

By displaying clear loading skeletons while the contract compiles and caching successful dispatches in IndexedDB, your users enjoy a fast, friction-free signing experience that seamlessly handles unreliable mobile data connections.

Frequently Asked Questions

What makes integrating e-signatures with Signbee take under 10 minutes?

Traditional e-signature providers require developers to install multi-megabyte language SDKs, navigate complex OAuth2 authorization code or JWT consent flows, upload pre-formatted PDFs, calculate visual coordinate tab coordinates, and manage multi-step envelope states. Signbee eliminates these layers by providing a single REST endpoint (POST /api/v1/send) that accepts plain Markdown or raw PDF bytes. The API handles server-side document rendering, email dispatch, signature capture, and cryptographic certificate generation in one atomic call.

Do I need to install an external npm or pip package to use Signbee?

No external SDK is required or recommended. Because Signbee exposes a clean, standard REST API, you can execute dispatches using your language's native HTTP primitives: standard fetch() in Node.js and Next.js, httpx orrequests in Python, net/http in Go, or curl in terminal scripts. This prevents dependency bloat, reduces serverless bundle sizes, and eliminates SDK version incompatibilities.

How does the recipient sign the document after dispatch?

The recipient receives an automated, unbranded email invitation containing a secure signing link. Clicking the link opens a responsive web ceremony in any mobile or desktop browser without requiring the recipient to install apps or register for an account. The signer reviews the document, draws or types their legal signature, and confirms acceptance. Upon completion, both parties immediately receive a final PDF copy accompanied by an evidentiary Certificate of Completion.

Are documents signed via this 10-minute integration legally enforceable?

Yes. Every agreement executed through Signbee conforms to the United States ESIGN Act (2000), the Uniform Electronic Transactions Act (UETA across 49 states), the EU eIDAS Regulation (Simple Electronic Signature tier), and the UK Electronic Communications Act 2000. Each completed agreement includes a cryptographic SHA-256 hash sealing the document content against post-signing alteration, alongside UTC timestamps and signer IP addresses admissible under Federal Rules of Evidence Rule 902.

Ship Document Signing in Your App Today

One REST endpoint. Free tier includes 5 documents per month with zero setup fees.