Guias

How to Add E-Signatures to Your Base44 App

Base44 apps handle real business processes: proposals, service agreements, intake forms, onboarding paperwork. None of that closes cleanly without a signature, and Base44 doesn't ship one. Firma.dev fills that gap with an e-signature API you can wire into any Base44 app in an afternoon, not a sprint.

How it works (without the technical details)

Firma.dev gives you two ways to connect a Base44 app, and which one you pick depends on how many apps you're running.

If it's a single app, you write a backend function: a small piece of TypeScript that calls Firma.dev's REST API to create a signing request, send it, and listen for the signature coming back. Base44 keeps your API key locked in a secret, so it never touches the browser.

If you're running a workspace with multiple apps, you register Firma.dev once as a custom integration using its OpenAPI spec. After that, every app in the workspace can call Firma.dev endpoints directly through the Base44 SDK, no separate function per app.

Either path ends the same way: a signer opens the document, signs it embedded right inside your app, and a webhook tells your backend the moment it's done.

What you need first

  • A Firma.dev account with an API key

  • A Base44 app on the Builder plan or higher (required for backend functions and custom integrations)

  • A Firma.dev template with signing fields already set up

Path 1: backend functions

This is the more flexible route, and the one to reach for if you're wiring up a single app.

Store the key. In the Base44 editor, go to Dashboard → Secrets, add a new secret, and name it FIRMA_API_KEY. Never put the key in frontend code, only backend functions should ever see it.

Write the function. Under Dashboard → Code → Functions, create a new function (or have the Base44 AI chat generate one). This is the exact example from Firma.dev's docs, creating and sending a signing request from a template in a single call:

import { createClientFromRequest } from "npm:@base44/sdk";

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

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req);
  const user = await base44.auth.me();

  if (!user) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { template_id, signer_email, signer_first_name, signer_last_name } =
    await req.json();

  const apiKey = Deno.env.get("FIRMA_API_KEY");

  // Create and send a signing request from a template in one call
  const response = await fetch(
    `${FIRMA_API}/signing-requests/create-and-send`,
    {
      method: "POST",
      headers: {
        Authorization: apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        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 Response.json({ error: data }, { status: response.status });
  }

  return Response.json({
    signing_request_id: data.id,
    status: "sent",
  });
});
import { createClientFromRequest } from "npm:@base44/sdk";

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

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req);
  const user = await base44.auth.me();

  if (!user) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { template_id, signer_email, signer_first_name, signer_last_name } =
    await req.json();

  const apiKey = Deno.env.get("FIRMA_API_KEY");

  // Create and send a signing request from a template in one call
  const response = await fetch(
    `${FIRMA_API}/signing-requests/create-and-send`,
    {
      method: "POST",
      headers: {
        Authorization: apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        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 Response.json({ error: data }, { status: response.status });
  }

  return Response.json({
    signing_request_id: data.id,
    status: "sent",
  });
});
import { createClientFromRequest } from "npm:@base44/sdk";

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

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req);
  const user = await base44.auth.me();

  if (!user) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { template_id, signer_email, signer_first_name, signer_last_name } =
    await req.json();

  const apiKey = Deno.env.get("FIRMA_API_KEY");

  // Create and send a signing request from a template in one call
  const response = await fetch(
    `${FIRMA_API}/signing-requests/create-and-send`,
    {
      method: "POST",
      headers: {
        Authorization: apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        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 Response.json({ error: data }, { status: response.status });
  }

  return Response.json({
    signing_request_id: data.id,
    status: "sent",
  });
});

If you'd rather review a request before it goes out, Firma.dev's docs note the alternative: POST /signing-requests to create a draft, then POST /signing-requests/{id}/send separately.

Connect it to the UI. Call the function from the frontend with the Base44 SDK:

import { base44 } from "@/api/base44Client";

const response = await base44.functions.invoke("sendSigningRequest", {
  template_id: "your-template-id",
  signer_email: "alice@example.com",
  signer_first_name: "Alice",
  signer_last_name: "Johnson",
});
import { base44 } from "@/api/base44Client";

const response = await base44.functions.invoke("sendSigningRequest", {
  template_id: "your-template-id",
  signer_email: "alice@example.com",
  signer_first_name: "Alice",
  signer_last_name: "Johnson",
});
import { base44 } from "@/api/base44Client";

const response = await base44.functions.invoke("sendSigningRequest", {
  template_id: "your-template-id",
  signer_email: "alice@example.com",
  signer_first_name: "Alice",
  signer_last_name: "Johnson",
});

Or just tell the Base44 AI chat what you want: "When the user clicks 'Send Contract', call the sendSigningRequest function with the template ID, signer email, and name from the form."

Track completions with a webhook. Create a backend function to receive the event, then register it in Firma.dev under Settings → Webhooks, pointing at https://<your-app-domain>/functions/<function-name>.

import { createClientFromRequest } from "npm:@base44/sdk";

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req);
  const payload = await req.json();
  const { type, data } = payload;

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

    // Update your app's database or trigger the next step
    // in your workflow using the Base44 entities SDK
  }

  return Response.json({ received: true });
});
import { createClientFromRequest } from "npm:@base44/sdk";

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req);
  const payload = await req.json();
  const { type, data } = payload;

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

    // Update your app's database or trigger the next step
    // in your workflow using the Base44 entities SDK
  }

  return Response.json({ received: true });
});
import { createClientFromRequest } from "npm:@base44/sdk";

Deno.serve(async (req) => {
  const base44 = createClientFromRequest(req);
  const payload = await req.json();
  const { type, data } = payload;

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

    // Update your app's database or trigger the next step
    // in your workflow using the Base44 entities SDK
  }

  return Response.json({ received: true });
});

Path 2: workspace-level OpenAPI integration

Running more than one Base44 app? Set this up once and every app in the workspace inherits it.

From your profile icon, go to Settings → Integrations → New Integration → From URL, and paste Firma.dev's OpenAPI spec URL (check Firma.dev's API changelog for the current version). Pick the endpoints you need, up to 30, commonly:

  • POST /signing-requests — create a signing request from a template or document

  • POST /signing-requests/create-and-send — create and send in one call

  • GET /signing-requests/{id} — check status

  • GET /signing-requests — list signing requests

  • POST /templates/{id}/duplicate — duplicate a template into a signing request

  • GET /templates — list available templates

For authentication, set the base URL to https://api.firma.dev/functions/v1/signing-request-api, then add a custom header named Authorization with your Firma.dev API key as the value. Base44 encrypts and stores it at the workspace level, it never reaches the browser.

From there, any app calls Firma.dev directly:

const result = await base44.integrations.custom.call(
  "firma",
  "post:/signing-requests/create-and-send",
  {
    payload: {
      template_id: templateId,
      recipients: [
        {
          first_name: "Alice",
          last_name: "Johnson",
          email: "alice@example.com",
          designation: "Signer",
          order: 1,
        },
      ],
    },
  }
);
const result = await base44.integrations.custom.call(
  "firma",
  "post:/signing-requests/create-and-send",
  {
    payload: {
      template_id: templateId,
      recipients: [
        {
          first_name: "Alice",
          last_name: "Johnson",
          email: "alice@example.com",
          designation: "Signer",
          order: 1,
        },
      ],
    },
  }
);
const result = await base44.integrations.custom.call(
  "firma",
  "post:/signing-requests/create-and-send",
  {
    payload: {
      template_id: templateId,
      recipients: [
        {
          first_name: "Alice",
          last_name: "Johnson",
          email: "alice@example.com",
          designation: "Signer",
          order: 1,
        },
      ],
    },
  }
);

Ask the Base44 AI chat to wire up Firma.dev calls here too, it detects the workspace integration and uses it on its own.

Embedded signing

For apps where you want the signer to complete the document without leaving your UI, pull the signing_request_user_id from the API response and load it in an iframe:

<iframe
  src="https://app.firma.dev/signing/{signing_request_user_id}"
  style="width:100%;height:900px;border:0;"
  allow="camera;microphone;clipboard-write"
  title="Document Signing"
></iframe>
<iframe
  src="https://app.firma.dev/signing/{signing_request_user_id}"
  style="width:100%;height:900px;border:0;"
  allow="camera;microphone;clipboard-write"
  title="Document Signing"
></iframe>
<iframe
  src="https://app.firma.dev/signing/{signing_request_user_id}"
  style="width:100%;height:900px;border:0;"
  allow="camera;microphone;clipboard-write"
  title="Document Signing"
></iframe>

What you get out of the box

Every signing request built this way inhertis Firma.dev's actual product, not a stripped-down API wrapper: an embeddable template editor for building documents, Customer Workspaces if you're running a multi-tenant Base44 app, and legal validity in 55+ countries under SES and AES (eIDAS-aligned; Firma.dev doesn't offer QES yet).

The pricing makes sense for builders

Base44 apps are usually early-stage, which makes per-seat signing tools a bad fit before you even have a full team. Firma.dev prices per envelope instead: €0.049 (~5¢), pay-as-you-go, no monthly minimum, no contract. Against DocuSign's per-user pricing, that's 97-99% cheaper at any volume you're likely to hit while building.

Send this to your developer (or do it yourself)

Both paths above are copy-pasteable, and most Base44 builders will have this running in under an hour. If you want the AI chat to do the wiring, point it at Firma.dev's Base44 integration guide and it can generate the functions directly. Firma.dev also has a Docs MCP server you can add under Account Settings → MCP Connections so the AI chat can pull accurate API details while you build.

Get started with Firma.dev for free, no credit card required.

  1. Cabeçalho

Imagem de Fundo

Pronto para adicionar assinaturas eletrónicas à sua aplicação?

Comece gratuitamente. Não é necessário cartão de crédito. Pague apenas 0,049 € por envelope quando estiver pronto para começar.

Imagem de Fundo

Pronto para adicionar assinaturas eletrónicas à sua aplicação?

Comece gratuitamente. Não é necessário cartão de crédito. Pague apenas 0,049 € por envelope quando estiver pronto para começar.

Imagem de Fundo

Pronto para adicionar assinaturas eletrónicas à sua aplicação?

Comece gratuitamente. Não é necessário cartão de crédito. Pague apenas 0,049 € por envelope quando estiver pronto para começar.