> ## 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.

# Cloudflare

> Add legally binding e-signatures to Cloudflare Workers or Pages apps using the Firma API.

Firma lets you add legally binding e-signatures to any application running on Cloudflare. Call the Firma API from Workers or Pages Functions to create templates, send signing requests, and track completions via webhooks.

This guide covers two integration paths:

1. **Workers** — Standalone serverless functions that call the Firma REST API. Best for APIs, microservices, or apps that don't use Cloudflare Pages.
2. **Pages Functions** — File-based serverless functions that deploy alongside your Cloudflare Pages site. Best if you already have a Pages project and want to keep everything in one repo.

Both paths use the same Firma API endpoints. The difference is how you structure and deploy your code.

## Prerequisites

* A [Firma account](https://app.firma.dev) with an API key
* The [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) installed (`npm install -g wrangler`)
* At least one Firma template with signing fields configured

<Note>
  Firma uses the raw API key as the `Authorization` header value. Do not prefix it with `Bearer`. This differs from many other APIs.
</Note>

## Choose your path

<Tabs>
  <Tab title="Workers">
    ### Step 1: Create a Worker project

    If you don't have a Workers project yet, scaffold one:

    ```bash theme={null}
    npm create cloudflare@latest -- firma-signing
    ```

    Select **"Hello World" Worker** when prompted, then choose JavaScript or TypeScript. Once scaffolding completes:

    ```bash theme={null}
    cd firma-signing
    ```

    ### Step 2: Store your API key as a secret

    Add your Firma API key as an encrypted secret. Wrangler will prompt you to paste the value:

    ```bash theme={null}
    wrangler secret put FIRMA_API_KEY
    ```

    Never hardcode your API key in source files. Secrets are encrypted at rest and injected into your Worker's `env` binding at runtime.

    ### Step 3: Create a Worker to send signing requests

    Replace the contents of `src/index.js` (or `src/index.ts` if you chose TypeScript) with the following. This Worker accepts a POST request with signer details, calls the Firma `create-and-send` endpoint, and returns the signing request ID.

    ```javascript theme={null}
    const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api";

    export default {
      async fetch(request, env) {
        if (request.method === "OPTIONS") {
          return new Response(null, {
            headers: {
              "Access-Control-Allow-Origin": "*",
              "Access-Control-Allow-Methods": "POST, OPTIONS",
              "Access-Control-Allow-Headers": "Content-Type",
            },
          });
        }

        if (request.method !== "POST") {
          return Response.json({ error: "Method not allowed" }, { status: 405 });
        }

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

        const response = await fetch(
          `${FIRMA_API}/signing-requests/create-and-send`,
          {
            method: "POST",
            headers: {
              Authorization: env.FIRMA_API_KEY,
              "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 Response.json({ error: data }, { status: response.status });
        }

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

    <Tip>
      The `create-and-send` endpoint creates the signing request and sends it to recipients in a single call. 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.
    </Tip>

    ### Step 4: Deploy

    ```bash theme={null}
    wrangler deploy
    ```

    Wrangler will output your Worker's URL (something like `https://firma-signing.<your-subdomain>.workers.dev`). Your frontend can now POST signer details to that URL.

    ### Webhook handling

    To track when documents are signed, create a second Worker that receives Firma webhook events. In the Firma dashboard under **Settings → Webhooks**, register a webhook pointing to this Worker's URL.

    ```javascript theme={null}
    export default {
      async fetch(request, env) {
        if (request.method !== "POST") {
          return new Response("Method not allowed", { status: 405 });
        }

        const payload = await request.json();
        const { type, data } = payload;

        if (type === "signing_request.completed") {
          const signingRequestId = data.signing_request.id;
          console.log(`Signing request ${signingRequestId} completed`);
        }

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

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

    <Warning>
      For production use, always verify the webhook signature using your Firma webhook signing secret. See the [webhooks guide](/guides/webhooks) for implementation details.
    </Warning>
  </Tab>

  <Tab title="Pages Functions">
    ### Step 1: Set up your Pages project

    If you already have a Cloudflare Pages project, skip to the next step. Otherwise, create one:

    ```bash theme={null}
    mkdir firma-app && cd firma-app
    npm init -y
    mkdir public functions
    echo "<h1>Firma App</h1>" > public/index.html
    ```

    Pages Functions live in the `/functions` directory. Each file maps to a URL path automatically, so `functions/api/send-signing-request.js` becomes `/api/send-signing-request`.

    ### Step 2: Store your API key as a secret

    Add your Firma API key as an encrypted Pages secret:

    ```bash theme={null}
    wrangler pages secret put FIRMA_API_KEY --project-name your-project-name
    ```

    Alternatively, go to **Cloudflare Dashboard → Pages → your project → Settings → Environment variables** and add `FIRMA_API_KEY` as an encrypted variable.

    ### Step 3: Create a Pages Function to send signing requests

    Create a new file at `functions/api/send-signing-request.js`:

    ```javascript theme={null}
    const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api";

    export async function onRequestPost(context) {
      const { name, template_id, signer_email, signer_first_name, signer_last_name } =
        await context.request.json();

      const response = await fetch(
        `${FIRMA_API}/signing-requests/create-and-send`,
        {
          method: "POST",
          headers: {
            Authorization: context.env.FIRMA_API_KEY,
            "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 Response.json({ error: data }, { status: response.status });
      }

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

    <Tip>
      Pages Functions export named handlers per HTTP method: `onRequestPost`, `onRequestGet`, etc. If you need to handle multiple methods, export `onRequest` and check `context.request.method` inside.
    </Tip>

    ### Step 4: Deploy

    If your Pages project is connected to a Git repo, push your changes and Cloudflare deploys automatically. For manual deploys:

    ```bash theme={null}
    wrangler pages deploy public --project-name your-project-name
    ```

    Your function is now live at `https://your-project.pages.dev/api/send-signing-request`.

    ### Webhook handling

    Create a second Pages Function at `functions/api/firma-webhook.js` to handle incoming Firma webhook events. Register this URL in the Firma dashboard under **Settings → Webhooks**.

    ```javascript theme={null}
    export async function onRequestPost(context) {
      const payload = await context.request.json();
      const { type, data } = payload;

      if (type === "signing_request.completed") {
        const signingRequestId = data.signing_request.id;
        console.log(`Signing request ${signingRequestId} completed`);
      }

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

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

    <Warning>
      For production use, always verify the webhook signature using your Firma webhook signing secret. See the [webhooks guide](/guides/webhooks) for implementation details.
    </Warning>
  </Tab>
</Tabs>

## Embedded signing

For apps where signers complete documents directly in your UI, 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`. Load it in an iframe:

```html theme={null}
<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>
```

See the [embedded signing guide](/guides/embeddable-signing) for full setup instructions including security best practices.

## Bonus: MCP connection for AI-assisted building

Firma offers a [Docs MCP server](/guides/mcp) that AI coding tools can connect to. If you use an AI assistant while building your Cloudflare integration, connecting the MCP server lets it search Firma documentation and generate accurate API calls.

Add the MCP server URL to your tool's configuration:

```text theme={null}
https://docs.firma.dev/mcp
```

This is for the development experience only and does not affect your deployed app.

## Next steps

* [API authentication](/guides/authentication) — API keys and workspace scoping
* [Webhooks guide](/guides/webhooks) — event types, payloads, and signature verification
* [Embedded signing](/guides/embeddable-signing) — in-app signing experience
* [Creating workspaces](/guides/creating-workspaces) — multi-tenant setups for SaaS apps
* [Complete setup guide](/guides/complete-setup-guide) — end-to-end Firma integration walkthrough
* [API reference](/api-reference) — full endpoint documentation
