Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.firma.dev/llms.txt

Use this file to discover all available pages before exploring further.

Firma lets you add legally binding e-signatures to any v0 application. Use Next.js route handlers to call the Firma REST API, send signing requests, and receive webhook events when documents are signed. This guide walks through the full flow: storing your API key, sending a signing request, wiring it to your UI, and handling completion events.

Prerequisites

  • A Firma account with an API key
  • A v0 account with an active project (free tier works)
  • At least one Firma template with signing fields configured
Firma uses the raw API key as the Authorization header value — do not prefix it with Bearer. This differs from many other APIs.

Step 1: Store your API key as an environment variable

v0 manages environment variables through the Vars panel in the chat sidebar. Values are synced with the connected Vercel project, so the same secret works in previews and in production.
  1. Open your v0 project and click the Vars tab in the sidebar
  2. Click Add Variable
  3. Set the key to FIRMA_API_KEY and paste your Firma API key as the value
  4. Save
The variable is now available as process.env.FIRMA_API_KEY in any server-side code v0 generates.
Never expose your API key in client components. Always call the Firma API from route handlers or server actions, not from "use client" files.

Step 2: Create a route handler to send signing requests

Ask v0 to create a route handler, or add the file directly. v0 generates Next.js App Router code by default, so route handlers live in app/api/<route-name>/route.ts. This example creates and sends a signing request from a template in a single call using the create-and-send endpoint:
// app/api/send-signing-request/route.ts
import { NextRequest, NextResponse } from "next/server";

const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api";

export async function POST(req: NextRequest) {
  const { name, template_id, signer_email, signer_first_name, signer_last_name } =
    await req.json();

  const apiKey = process.env.FIRMA_API_KEY;
  if (!apiKey) {
    return NextResponse.json(
      { error: "FIRMA_API_KEY not configured" },
      { status: 500 }
    );
  }

  const response = await fetch(
    `${FIRMA_API}/signing-requests/create-and-send`,
    {
      method: "POST",
      headers: {
        Authorization: apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name,
        template_id,
        recipients: [
          {
            first_name: signer_first_name,
            last_name: signer_last_name,
            email: signer_email,
            designation: "Signer",
            order: 1,
          },
        ],
      }),
    }
  );

  const data = await response.json();

  if (!response.ok) {
    return NextResponse.json({ error: data }, { status: response.status });
  }

  return NextResponse.json({
    signing_request_id: data.id,
    status: "sent",
  });
}
The create-and-send endpoint creates the signing request and sends it to recipients atomically. If you need to review or modify the request before sending, use POST /signing-requests to create a draft, then POST /signing-requests/{id}/send separately.

Step 3: Call the route handler from your UI

From a client component, POST to your new route when the user completes the form:
// components/send-contract-button.tsx
"use client";

import { useState } from "react";

export function SendContractButton({ templateId }: { templateId: string }) {
  const [loading, setLoading] = useState(false);

  async function handleSend() {
    setLoading(true);
    const res = await fetch("/api/send-signing-request", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name: "NDA for Alice Johnson",
        template_id: templateId,
        signer_email: "alice@example.com",
        signer_first_name: "Alice",
        signer_last_name: "Johnson",
      }),
    });
    const data = await res.json();
    setLoading(false);
    console.log(data);
  }

  return (
    <button onClick={handleSend} disabled={loading}>
      {loading ? "Sending..." : "Send contract"}
    </button>
  );
}
You can also ask v0’s chat to wire this up: “When the user submits the contract form, call /api/send-signing-request with the template ID, signer email, first name, and last name from the form.” v0 will generate the form, the button, and the fetch call together.

Webhook integration

To track when documents are signed, add a webhook route handler and register it in the Firma dashboard.
// app/api/webhooks/firma/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const payload = await req.json();
  const { type, data } = payload;

  if (type === "signing_request.completed") {
    const signingRequestId = data.signing_request.id;

    // Update your database, send a notification, or trigger
    // the next step in your workflow
    console.log(`Signing request ${signingRequestId} completed`);
  }

  if (type === "signing_request.recipient.signed") {
    const recipientEmail = data.recipient?.email;
    console.log(`${recipientEmail} signed the document`);
  }

  return NextResponse.json({ received: true });
}
Then register the webhook:
  1. Publish your app (see below) so the route is publicly reachable
  2. In the Firma dashboard under Settings → Webhooks, add a webhook pointing to https://<your-app-domain>/api/webhooks/firma
  3. Select the events you want to receive. See the webhooks guide for all event types and signature verification
For production use, always verify the webhook signature using your Firma webhook signing secret. See the webhooks guide for implementation details.

Publishing to Vercel

v0 projects connect to a Vercel project automatically. To push your app to production:
  1. Click Publish in the top right of the v0 interface, or connect an existing Vercel project from Project settings
  2. Environment variables set in the Vars panel sync to Vercel automatically. To scope values per environment (Production, Preview, Development), edit them under Project → Settings → Environment Variables in the Vercel dashboard
  3. Once published, your webhook URL will be https://<project-name>.vercel.app/api/webhooks/firma. Use that when registering the webhook in Firma
If you need a different Firma API key for preview vs. production, set FIRMA_API_KEY per environment in the Vercel dashboard. v0’s Vars panel writes to all environments by default.

Embedded signing

For apps where signers complete documents inside your UI instead of jumping to email, Firma provides an embeddable signing experience. The create-and-send response includes a first_signer.id (the signing_request_user_id) and a ready-made first_signer.signing_link. Render it in an iframe:
// components/embedded-signing.tsx
export function EmbeddedSigning({
  signingRequestUserId,
}: {
  signingRequestUserId: string;
}) {
  return (
    <iframe
      src={`https://app.firma.dev/signing/${signingRequestUserId}`}
      style={{ width: "100%", height: "900px", border: 0 }}
      allow="camera; microphone; clipboard-write"
      title="Document Signing"
    />
  );
}
See the embedded signing guide for full setup including security best practices.

Bonus: MCP connection for AI-assisted building

Firma offers a Docs MCP server that v0’s chat can connect to directly. When connected, v0 searches Firma documentation while generating code so it uses accurate endpoints, field names, and patterns. To set it up:
  1. In v0, click Add MCP on the prompt form or go to Settings → MCP Connections
  2. Add a custom MCP server with the URL: https://docs.firma.dev/mcp
  3. Save
From that point, prompts like “Add a Firma route handler that sends a signing request from template abc123 using the email in the form” will generate code that matches the current Firma API. This is a build-time aid and does not affect your deployed app.

Next steps