Back to Articles
Nuxt 4 & Nitro H3 Guide
•14 min read•Nuxt 4 • Nitro 3 • Vue 3 Composition API

Nuxt 4 & Nitro Server Routes: Add E-Signatures in 10 Minutes (2026)

A complete production blueprint for integrating legally binding electronic signatures into Nuxt 4 and Vue 3 applications using Nitro H3 event handlers, Zod payload validation, runtime secret isolation, and timing-safe HMAC-SHA256 webhooks.

Quick Answer (TL;DR)

Nuxt 4 + Nitro Server Architecture • Zero-SDK Edge Pattern

To add legally binding e-signatures to a Nuxt 4 application, store your Signbee API key inside runtimeConfig.signbeeApiKey in nuxt.config.ts and create a Nitro server route at server/api/contracts/send.post.ts. Validate incoming Vue form payloads using H3's readValidatedBody(event, schema.parse), compile your contract in CommonMark markdown, and dispatch via $fetch('https://signb.ee/api/v1/send'). Process real-time completion events in server/api/webhooks/signbee.post.ts using readRawBody(event) and HMAC-SHA256 verification. Explore our dedicated Nuxt E-Signature Integration Hub for quick-start templates.

0 KB Client & Edge SDK Bloat
H3 + Zod Type-Safe RPC
Cloudflare Pages & Vercel Ready

1. Why Nuxt 4 & Nitro Demand a Modern E-Signature Architecture

With the release of Nuxt 4 and its new app/ directory convention alongside the battle-tested Nitro server engine and H3 HTTP framework, full-stack Vue engineering has standardized around universal, edge-capable deployments. Modern Nuxt teams build everything from multi-tenant B2B SaaS onboarding portals and wealth-management dashboards to automated contractor management platforms that run seamlessly across Node.js containers, Vercel Serverless Functions, and Cloudflare Pages V8 isolates.

Yet when engineering teams attempt to bolt legacy electronic signature providers onto a Nuxt 4 stack, they immediately hit architectural friction. Traditional e-signature vendors designed their Node.js SDKs nearly a decade ago for monolithic Express servers. Installing legacy packages like docusign-esign pulls over 48 MB of CommonJS dependencies, old request polyfills, and native Node fs calls that crash Nitro's Rollup bundler when targeting Cloudflare Workers or Vercel Edge environments. Furthermore, as detailed in our DocuSign API Pricing Explained breakdown, legacy platforms force developers through a multi-week partner review process just to promote OAuth JWT keys to production.

By contrast, pairing Nuxt 4's native Nuxt server routes integration with Signbee's zero-SDK REST API eliminates every single point of bloat. Nitro already ships with UnJS's universal ofetch ($fetch) built right into the global server context. Instead of wrestling with multi-step XML/REST envelope creation, coordinate tabs, and token refresh loops, your Nitro handler dispatches a single JSON request to POST https://signb.ee/api/v1/send and returns a cryptographically tracked contract in under 190 milliseconds.

2. Benchmarking E-Signature APIs in Nuxt 4 & Nitro Environments

When evaluating the best e-signature APIs for developers in 2026, full-stack Vue teams should measure five concrete engineering metrics: server bundle overhead, Cloudflare/Edge compatibility, contract generation ergonomics, webhook verification complexity, and total cost of ownership. Below is the head-to-head benchmark across Signbee, legacy enterprise APIs, and self-hosted alternatives reviewed in our open-source e-signature API comparison:

Engineering MetricSignbee REST API + NitroDocuSign Node SDKDropbox Sign SDK
Nitro Bundle Impact0 KB (Native $fetch)~48.4 MB (CommonJS + deps)~14.2 MB (Axios + OpenAPI gen)
Cloudflare Pages / Workers100% Native Web FetchFails without Node fs polyfillsPartial (Requires node_compat)
Contract Authoring FormatCommonMark Markdown or Base64 PDFPDF + Anchor/X-Y Coordinate TabsPDF + Template Merge Fields
Median Dispatch Latency184 ms (Single POST)1,420 ms (Auth + Envelope + Tabs)890 ms (Multipart Upload)
Developer Pricing ModelFree Tier + $20/mo Unlimited$600+/yr + Per-Envelope Quotas$900+/yr API Tier Minimum

3. Nuxt 4 Directory Layout & Runtime Secret Isolation

In Nuxt 4, client-rendered and universal Vue files live inside the app/ directory, while backend API routes, webhooks, and server utilities reside in the root server/ directory. This clean physical separation guarantees that server-only business logic, database queries, and third-party API secrets never leak into client-side JavaScript chunks.

Before writing our H3 event handlers, configure nuxt.config.ts to register private runtime configuration slots for your Signbee API key and webhook signing secret. When you define keys at the root of runtimeConfig (outside runtimeConfig.public), Nuxt automatically binds them to environment variables prefixed with NUXT_ at runtime:

nuxt.config.ts & .env — Secret Isolation ConfigurationTypeScript
// nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
  runtimeConfig: {
    // Private server-only secrets (never bundled into client code)
    // Automatically overridden at runtime by NUXT_SIGNBEE_API_KEY
    // and NUXT_SIGNBEE_WEBHOOK_SECRET environment variables
    signbeeApiKey: "",
    signbeeWebhookSecret: "",
    signbeeSenderName: "Acme Cloud Legal",
    signbeeSenderEmail: "contracts@acmecloud.io",

    // Public keys exposed to Vue components via useRuntimeConfig().public
    public: {
      appUrl: "https://portal.acmecloud.io",
    },
  },
  devtools: { enabled: true },
});

// .env (Local Development — Never commit to Git)
// NUXT_SIGNBEE_API_KEY="signbee_live_9f8e7d6c5b4a3f2e1d0c"
// NUXT_SIGNBEE_WEBHOOK_SECRET="whsec_4a5b6c7d8e9f0a1b2c3d4e5f"

Why pass useRuntimeConfig(event) with the event parameter inside Nitro routes instead of calling process.env directly? On edge platforms like Cloudflare Pages and Workers, environment bindings are attached per-request to the worker execution context rather than a global Node process.env object. Passing the H3 event into useRuntimeConfig(event) ensures your Signbee integration works identically on Node.js 22, Bun, Deno, and Cloudflare Workers.

4. Building the Nitro Server Route: H3 Event Handler + Zod Validation

Next, let's build the server endpoint at server/api/contracts/send.post.ts. Naming the file with the .post.ts suffix instructs Nitro's file-system router to only accept HTTP POST requests on /api/contracts/send, automatically returning a 405 Method Not Allowed response for any other verb.

Inside our handler, we combine H3's readValidatedBody(event, schema.parse) utility with a strict Zod schema. If a client submits a malformed email address, negative contract value, or empty company name, H3 automatically halts execution and returns a structured 400 Bad Request error before touching the network. Once validated, we generate a dynamic CommonMark markdown agreement—complete with a Markdown pricing schedule table—and dispatch it to Signbee via $fetch:

server/api/contracts/send.post.ts — Nitro H3 Dispatch Handlerserver/api
// server/api/contracts/send.post.ts
import { z } from "zod";
import { defineEventHandler, readValidatedBody, createError } from "h3";

const SendContractSchema = z.object({
  clientCompany: z.string().min(2, "Company name is required").max(120),
  recipientName: z.string().min(2, "Recipient name is required").max(100),
  recipientEmail: z.string().email("Valid recipient email is required"),
  planTier: z.enum(["Growth", "Scale", "Enterprise"]),
  annualSeats: z.number().int().min(1).max(10000),
  annualValueUsd: z.number().positive(),
  customTerms: z.string().max(2000).optional(),
});

interface SignbeeSendResponse {
  id: string;
  status: "sent" | "pending_verification";
  title: string;
  signing_url?: string;
  verify_url: string;
  created_at: string;
}

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event);

  if (!config.signbeeApiKey) {
    throw createError({
      statusCode: 500,
      statusMessage: "Server configuration error: missing NUXT_SIGNBEE_API_KEY",
    });
  }

  // Validate request body with H3 + Zod
  const payload = await readValidatedBody(event, (body) =>
    SendContractSchema.parse(body)
  );

  const effectiveDate = new Date().toISOString().split("T")[0];
  const formattedPrice = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
  }).format(payload.annualValueUsd);

  // Compile clean CommonMark markdown (Signbee renders tables, headers & pagination into A4 PDF)
  const contractMarkdown = [
    `# Master SaaS Subscription Agreement — ${payload.clientCompany}`,
    "",
    `This Subscription Agreement ("Agreement") is entered into on **${effectiveDate}** between **${config.signbeeSenderName}** ("Provider") and **${payload.clientCompany}** ("Customer").`,
    "",
    "## 1. Subscription Order Schedule",
    "",
    "| Subscription Tier | Licensed Seats | Billing Cycle | Annual Contract Value |",
    "| :--- | :--- | :--- | :--- |",
    `| ${payload.planTier} Plan | ${payload.annualSeats} Seats | Annual Upfront | ${formattedPrice} |`,
    "",
    "## 2. Service Level & Data Protection",
    "Provider guarantees 99.95% monthly uptime across all production API endpoints and processes all personal data in strict accordance with the UK GDPR, EU GDPR, and standard contractual clauses.",
    "",
    payload.customTerms
      ? `## 3. Special Commercial Addendum\n\n${payload.customTerms}`
      : "## 3. Standard Commercial Terms\n\nThis Order Form is governed by the standard Master Terms published at portal.acmecloud.io/legal.",
    "",
    "## 4. Electronic Execution",
    "By executing this Agreement below, both parties acknowledge and agree that electronic signatures carry the same legal enforceability and admissibility as handwritten signatures under the US ESIGN Act, eIDAS, and the UK Electronic Communications Act 2000.",
  ].join("\n");

  try {
    const response = await $fetch<SignbeeSendResponse>(
      "https://signb.ee/api/v1/send",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${config.signbeeApiKey}`,
          "Content-Type": "application/json",
        },
        body: {
          title: `SaaS Subscription Agreement — ${payload.clientCompany}`,
          markdown: contractMarkdown,
          sender_name: config.signbeeSenderName,
          sender_email: config.signbeeSenderEmail,
          recipient_name: payload.recipientName,
          recipient_email: payload.recipientEmail,
          expires_in_days: 14,
          webhook_url: `${config.public.appUrl}/api/webhooks/signbee`,
        },
      }
    );

    return {
      ok: true,
      documentId: response.id,
      status: response.status,
      verifyUrl: response.verify_url,
      dispatchedAt: response.created_at,
    };
  } catch (err: any) {
    throw createError({
      statusCode: err?.response?.status || 502,
      statusMessage:
        err?.data?.error || "Failed to dispatch agreement via Signbee API",
    });
  }
});

Because Nitro automatically infers the return type of defineEventHandler, your frontend Vue components receive full TypeScript autocomplete for documentId, status, verifyUrl, and dispatchedAt without writing a single manual API client interface. If you are looking for an affordable e-signature API integration that avoids weeks of PDF template tagging, this markdown-first pattern cuts implementation time by over 85%.

5. Vue 3 Composition API Frontend: Type-Safe Contract Dispatch

On the client side, inside app/components/ContractDispatcher.vue, we use Vue 3's Composition API and Nuxt's global $fetch helper to submit the form. Notice that when a user clicks a button to trigger a state-changing mutation (such as generating and dispatching a legal contract), Nuxt 4 best practices recommend calling $fetch directly inside an event handler rather than wrapping the POST request in useFetch, which is intended for declarative SSR data fetching on component mount.

To prevent accidental double-submissions when a user double-clicks the submit button on a high-latency mobile connection, we track an isSubmitting ref that disables the button and renders an optimistic loading indicator while Nitro compiles the CommonMark document and communicates with Signbee:

app/components/ContractDispatcher.vue — Vue 3 Composition APIVue 3 SFC
// app/components/ContractDispatcher.vue (Vue 3 Setup Script & Template)
import { reactive, ref } from "vue";

const form = reactive({
  clientCompany: "Northwind Logistics Ltd",
  recipientName: "Elena Rostova",
  recipientEmail: "elena@northwind.io",
  planTier: "Scale" as const,
  annualSeats: 25,
  annualValueUsd: 18500,
  customTerms: "Includes dedicated SSO configuration and quarterly architecture reviews.",
});

const isSubmitting = ref(false);
const errorMessage = ref<string | null>(null);
const dispatchResult = ref<{
  documentId: string;
  verifyUrl: string;
  dispatchedAt: string;
} | null>(null);

export async function handleSendAgreement() {
  isSubmitting.value = true;
  errorMessage.value = null;

  try {
    const result = await $fetch("/api/contracts/send", {
      method: "POST",
      body: form,
    });
    dispatchResult.value = {
      documentId: result.documentId,
      verifyUrl: result.verifyUrl,
      dispatchedAt: result.dispatchedAt,
    };
  } catch (err: any) {
    errorMessage.value =
      err?.data?.statusMessage || "Unable to send contract for signature.";
  } finally {
    isSubmitting.value = false;
  }
}

/*
<template>
  <div class="p-6 rounded-2xl bg-zinc-900 border border-zinc-800 max-w-xl">
    <h3 class="text-lg font-bold text-zinc-100 mb-4">
      Dispatch Enterprise Order Form
    </h3>

    <form @submit.prevent="handleSendAgreement" class="space-y-4">
      <div>
        <label class="block text-xs font-mono text-zinc-400 mb-1">Client Company</label>
        <input v-model="form.clientCompany" required class="w-full px-3 py-2 rounded-lg bg-zinc-950 border border-zinc-800 text-zinc-100 text-sm" />
      </div>

      <div class="grid grid-cols-2 gap-3">
        <div>
          <label class="block text-xs font-mono text-zinc-400 mb-1">Signer Name</label>
          <input v-model="form.recipientName" required class="w-full px-3 py-2 rounded-lg bg-zinc-950 border border-zinc-800 text-zinc-100 text-sm" />
        </div>
        <div>
          <label class="block text-xs font-mono text-zinc-400 mb-1">Signer Email</label>
          <input v-model="form.recipientEmail" type="email" required class="w-full px-3 py-2 rounded-lg bg-zinc-950 border border-zinc-800 text-zinc-100 text-sm" />
        </div>
      </div>

      <button
        type="submit"
        :disabled="isSubmitting"
        class="w-full py-2.5 px-4 rounded-lg bg-amber-500 text-zinc-950 font-semibold text-sm hover:bg-amber-400 disabled:opacity-50"
      >
        {{ isSubmitting ? "Generating PDF & Dispatching..." : "Send Legally Binding Contract" }}
      </button>
    </form>

    <div v-if="dispatchResult" class="mt-4 p-4 rounded-xl bg-emerald-950/30 border border-emerald-500/30 text-xs text-emerald-300">
      Agreement dispatched! Document ID: {{ dispatchResult.documentId }}
    </div>
  </div>
</template>
*/

6. Timing-Safe Webhook Verification in Nitro (`readRawBody`)

Dispatching the contract is only half of a production SaaS workflow. When your customer signs the agreement on their mobile phone or desktop browser, your Nuxt 4 backend needs to automatically provision their account seats, unlock paid features, and archive the SHA-256 cryptographic certificate hash in PostgreSQL.

When Party B completes the signing ceremony, Signbee sends a webhook POST request to your webhook_url containing an X-Signbee-Signature header. A common mistake in Nuxt server routes is calling await readBody(event) and then running JSON.stringify(body) to compute the HMAC hash. Because JSON parsers normalize whitespace and floating-point formatting, re-serializing a parsed object alters the byte stream and breaks cryptographic verification.

Instead, always call H3's await readRawBody(event, "utf8") first to capture the exact wire bytes, verify the HMAC-SHA256 digest using crypto.timingSafeEqual, and only then parse the JSON payload:

server/api/webhooks/signbee.post.ts — Raw Body HMAC-SHA256 VerificationWebhook Endpoint
// server/api/webhooks/signbee.post.ts
import { createHmac, timingSafeEqual } from "node:crypto";
import { defineEventHandler, readRawBody, getHeader, createError } from "h3";

interface SignbeeWebhookEvent {
  event: "document.sent" | "document.viewed" | "document.signed" | "document.expired";
  document_id: string;
  title: string;
  recipient_email: string;
  signed_at?: string;
  sha256_hash?: string;
  signed_pdf_url?: string;
  verify_url: string;
}

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event);
  const signatureHeader = getHeader(event, "x-signbee-signature");

  if (!signatureHeader || !config.signbeeWebhookSecret) {
    throw createError({
      statusCode: 401,
      statusMessage: "Missing webhook signature or secret configuration",
    });
  }

  // CRITICAL: Read raw unparsed UTF-8 bytes before any JSON parsing
  const rawBody = await readRawBody(event, "utf8");
  if (!rawBody) {
    throw createError({
      statusCode: 400,
      statusMessage: "Empty webhook payload",
    });
  }

  // Compute expected HMAC-SHA256 signature
  const expectedSignature = createHmac("sha256", config.signbeeWebhookSecret)
    .update(rawBody, "utf8")
    .digest("hex");

  const cleanProvided = signatureHeader.replace(/^sha256=/, "");
  const providedBuffer = Buffer.from(cleanProvided, "hex");
  const expectedBuffer = Buffer.from(expectedSignature, "hex");

  if (
    providedBuffer.length !== expectedBuffer.length ||
    !timingSafeEqual(providedBuffer, expectedBuffer)
  ) {
    throw createError({
      statusCode: 401,
      statusMessage: "Invalid X-Signbee-Signature digest",
    });
  }

  // Safe to parse JSON after cryptographic verification passes
  const payload = JSON.parse(rawBody) as SignbeeWebhookEvent;

  if (payload.event === "document.signed") {
    // Idempotently update tenant status in your database (Drizzle / Prisma)
    console.info(
      `[Signbee Webhook] Document ${payload.document_id} signed by ${payload.recipient_email}. SHA-256: ${payload.sha256_hash}`
    );
  }

  return { received: true, documentId: payload.document_id };
});

7. Deploying Nuxt 4 + Signbee Across Edge & Serverless Presets

One of Nitro's greatest strengths is its zero-configuration deployment presets. Whether you deploy your Nuxt 4 application to Vercel, Cloudflare Pages, Netlify Edge, or a self-managed Docker container, your Signbee contract pipeline adapts with minimal configuration. If you are building a custom portal, our guide on white-label e-signature API workflows explains how to pair these edge deployments with branded email notifications and custom verification links.

Here is how Nitro's top production presets handle HTTP outbound calls, HMAC crypto verification, and cold-start latency when communicating with Signbee:

Nitro PresetRuntime EngineHMAC Crypto APICold StartEnv Variable Mapping
cloudflare-pagesworkerd (V8 Isolates)node:crypto (nodejs_compat) or Web Crypto< 5 msCloudflare Pages Encrypted Secrets
vercelNode.js 22 ServerlessNative node:crypto~110 msVercel Project Environment Variables
vercel-edgeEdge Runtime (V8)crypto.subtle (Web Crypto API)< 12 msVercel Edge Env Bindings
node-serverNode.js 22 LTS / Bun 1.2Native node:crypto0 ms (Warm Process)Docker / Systemd NUXT_SIGNBEE_*

8. Real-Time Status Polling, Drizzle ORM Persistence & SaaS Unit Economics

In addition to asynchronous webhook delivery, many B2B SaaS onboarding flows keep the customer inside an interactive Nuxt 4 wizard while their counterparty or legal co-signer executes the document. You can build a lightweight status polling route at server/api/contracts/[id].get.ts that queries GET https://signb.ee/api/v1/documents/{id} using H3's getRouterParam(event, "id") helper. Because Signbee caches document metadata at the edge and returns sub-50ms status responses, your Vue 3 frontend can poll or refresh contract state via useFetch(`/api/contracts/${documentId}`) whenever the browser tab regains focus.

Before shipping your Nuxt 4 e-signature integration to production, verify these four operational safeguards across your Nitro server layer and database schema:

  • Idempotent Webhook Storage in PostgreSQL or Turso: Network retries and load balancer failovers can occasionally deliver the same document.signed webhook payload more than once. Always store the Signbee document_id and sha256_hash in a unique indexed column using Drizzle ORM or Prisma, and wrap your tenant activation logic in an idempotent ON CONFLICT DO NOTHING transaction so downstream seat provisioning and billing triggers execute exactly once.
  • Nitro Route Rules & Session Guard Separation: Configure routeRules and server middleware in server/middleware/auth.ts so that /api/contracts/** requires an authenticated user session (via nuxt-auth-utils, BetterAuth, or Supabase Auth), while explicitly exempting /api/webhooks/signbee from cookie session checks so Signbee's automated webhook workers can authenticate via the cryptographic X-Signbee-Signature header.
  • Dual Markdown & Base64 PDF Support for Enterprise Legal Teams: While CommonMark markdown handles 95% of SaaS order forms, partner agreements, mutual NDAs, and consulting statements of work with zero coordinate maintenance, enterprise procurement teams occasionally supply third-party paper on pre-formatted PDF letterhead. Your Nitro route can accept an uploaded PDF file via H3's readMultipartFormData(event), convert the buffer to a base64 string, and pass it via the pdf_base64 field on the exact same /api/v1/send endpoint.
  • Zero-Cost Staging & Predictable High-Volume Unit Economics: Start building and testing locally today with our free e-signature API tier, which includes 3 legally binding documents every month with full API key and webhook functionality and no credit card required. As your Nuxt application scales into production, review our 2026 e-signature API pricing comparison to see how Signbee's flat $20/month unlimited Pro plan eliminates the $2.50 to $6.00 per-envelope overage fees charged by legacy vendors.

9. Frequently Asked Questions: Nuxt 4 & Nitro E-Signatures

How do you integrate e-signatures into a Nuxt 4 application using Nitro server routes?

To integrate e-signatures into Nuxt 4, create a server route in server/api/contracts/send.post.ts using Nitro's defineEventHandler and H3's readValidatedBody with a Zod schema. Load your private Signbee API key securely via useRuntimeConfig(event).signbeeApiKey so it is never exposed to the browser bundle. Send an authenticated POST request to https://signb.ee/api/v1/send containing either dynamic CommonMark markdown or a base64 PDF payload. Your Vue 3 component calls this endpoint via $fetch('/api/contracts/send', { method: 'POST', body }) with end-to-end TypeScript inference.

Why should Nuxt 4 developers avoid legacy e-signature Node.js SDKs in Nitro edge runtimes?

Legacy Node.js e-signature SDKs from providers like DocuSign and Adobe Sign depend heavily on CommonJS modules, Node-specific file system APIs (fs, path, stream), and bloated transitive dependencies exceeding 45 MB. When deploying Nuxt 4 applications to V8 isolate edge environments such as Cloudflare Pages, Cloudflare Workers, or Vercel Edge, these legacy SDKs fail during bundling or breach the strict 1 MB to 10 MB compressed worker size limits. Signbee uses a zero-SDK REST architecture over standard Web Fetch, resulting in 0 KB of extra client or edge bundle weight.

How do you verify Signbee HMAC-SHA256 webhook signatures in Nuxt Nitro H3 handlers?

In Nuxt Nitro, you must read the unparsed request stream using H3's readRawBody(event, 'utf8') helper inside server/api/webhooks/signbee.post.ts before calling readBody(event). If you parse the JSON first and re-stringify it, whitespace or key-ordering differences can invalidate the cryptographic signature. Compute an HMAC-SHA256 digest over the raw UTF-8 string using your webhook secret from useRuntimeConfig(event).signbeeWebhookSecret, and compare it against the X-Signbee-Signature header using Node's crypto.timingSafeEqual or the Web Crypto API subtle.verify method to prevent timing side-channel attacks.

How does useRuntimeConfig protect Signbee API keys in Nuxt 4?

Nuxt 4 partitions runtime configuration into private server-only keys and public client-exposed keys inside nuxt.config.ts. Any key defined at the top level of runtimeConfig—such as signbeeApiKey and signbeeWebhookSecret—is stripped from the client-side Vite bundle during compilation and is only accessible inside server/ routes, middleware, and plugins via useRuntimeConfig(event). At deployment time, Nitro automatically maps NUXT_SIGNBEE_API_KEY and NUXT_SIGNBEE_WEBHOOK_SECRET environment variables into these runtime slots without requiring a rebuild across staging and production environments.

Can Nuxt 4 generate dynamic legal contracts from Vue data without PDF coordinate tags?

Yes. Instead of uploading static PDF templates and calculating brittle x/y pixel coordinates for every signature and date box, your Nitro server route can interpolate validated form data directly into a CommonMark markdown template string. When your server route posts that markdown string to POST https://signb.ee/api/v1/send, Signbee's rendering engine compiles it into an A4 PDF with responsive tables, legal clause pagination, widow and orphan protection, Party A's immediate cryptographic signature, and a tamper-evident SHA-256 audit trail.

What does it cost to run Signbee e-signatures in a production Nuxt 4 SaaS application?

Signbee offers a developer-first free tier that includes 3 legally binding documents per month with full REST API and webhook access, plus a flat $20/month Pro plan that includes unlimited documents, custom branding, and team collaboration. Unlike legacy enterprise platforms that charge $480 to $5,000+ per year with strict envelope quotas and per-envelope overage penalties, Signbee charges $0.00 per envelope on Pro, making unit economics predictable for high-volume Nuxt 4 SaaS onboarding and B2B client portals.

Ship E-Signatures in Your Nuxt 4 App Today

Generate your Signbee API key in 30 seconds and dispatch your first CommonMark or PDF contract from a Nitro server route with zero SDK bloat.