Skip to main content
Add legally binding e-signatures to any Salesforce org using Flow, Apex, or Agentforce. Send signing requests off a record update, embed signing in Lightning, and route webhook events back to Salesforce records. This guide covers three integration paths:
  1. Flow with External Services — No-code. Register Firma’s OpenAPI spec once, then invoke Create and send signing request from any Flow. Best for admins.
  2. Apex with Named Credentials — Code. Call Firma directly from Apex classes, Flow Actions, or LWC controllers. Best for developers who want full control.
  3. Agentforce action — Expose Firma as an Action on an Agentforce agent so it can send signing requests in response to a user prompt or a trigger.
All three paths share the same Named Credential, so set that up first.

Prerequisites

  • A Firma account with an API key
  • A Salesforce org (Enterprise, Unlimited, or Developer edition) with permission to create Named Credentials and External Services
  • 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 0: Store the Firma API key as a Named Credential

Named Credentials are Salesforce’s secure pattern for storing endpoint URLs and auth headers. Every other section in this guide references it.
  1. In Setup, search for Named Credentials and open the page
  2. Under External Credentials, click New:
    • Label: Firma
    • Name: Firma
    • Authentication Protocol: Custom
  3. Click into the new External Credential, scroll to Principals, and click New:
    • Parameter Name: Default
    • Sequence Number: 1
    • Identity Type: Named Principal
    • Authentication Parameters: add one parameter
      • Name: ApiKey
      • Value: your Firma API key
  4. Scroll to Custom Headers and add one:
    • Name: Authorization
    • Value: {!$Credential.Firma.ApiKey}
  5. Back under Named Credentials, click New:
    • Label: Firma API
    • Name: Firma_API
    • URL: https://api.firma.dev/functions/v1/signing-request-api
    • External Credential: Firma
    • Allowed Namespaces: leave default
  6. Save
Never hardcode the API key in Apex, Flow, or a Custom Setting. Always reference it through the Named Credential so it stays encrypted at rest and rotation only requires editing one record.

Path 1: Flow with External Services

The no-code path. You import Firma’s OpenAPI spec once, and the actions become available as invocable steps in any Flow.

Step 1: Register Firma as an External Service

  1. In Setup, search for External Services and click Add an External Service
  2. Choose From API Specification, then Save and Next
  3. Configure:
    • External Service Name: Firma
    • Select a Named Credential: Firma_API
    • Service Schema: Upload from Local or paste the JSON
  4. Paste the Firma OpenAPI spec from https://docs.firma.dev/api-reference/v01.26.00/openapi-v01.26.00.json
  5. Click Save and Next
  6. Select the operations you want available in Flow. Common picks:
    • POST /signing-requests/create-and-send
    • POST /signing-requests
    • POST /signing-requests/{id}/send
    • GET /signing-requests/{id}
    • GET /templates
  7. Click Save and Next, then Finish
Check the Firma API changelog for the latest spec URL. Salesforce caches the spec at registration time, so re-register the External Service whenever you want new endpoints.

Step 2: Build the Flow

  1. In Setup, open Flows and create a new Flow (Record-Triggered is the most common)
  2. Choose your trigger object (Opportunity, Contract, Quote, or a custom object) and the condition that should send a signing request (e.g., StageName equals Closed Won)
  3. Add an Action element
  4. Search for Firma and pick Create and send signing request
  5. Map the inputs:
    • template_id: a Custom Metadata value, a record field, or a hardcoded template ID
    • recipients: build a collection of recipient records. Required fields are first_name and email. Optional fields include last_name, designation (defaults to Signer), order, company, title, phone_number, and address fields. The easiest way is an Assignment element that builds the collection from the triggering record’s contact fields.
  6. Save and activate
When the trigger fires, Salesforce calls Firma over the Named Credential and Firma sends the document to the signer.

Path 2: Apex with Named Credentials

For developers who want full control, call Firma directly from Apex. Use the Named Credential as the endpoint base so the API key stays encrypted.
public with sharing class FirmaService {

    private static final String FIRMA_BASE = 'callout:Firma_API';

    public class Recipient {
        public String first_name;
        public String last_name;
        public String email;
        public String designation;
        public Integer order;
    }

    public static String createAndSend(String templateId, Recipient signer) {
        signer.designation = 'Signer';
        signer.order = 1;

        Map<String, Object> body = new Map<String, Object>{
            'template_id' => templateId,
            'recipients' => new List<Recipient>{ signer }
        };

        HttpRequest req = new HttpRequest();
        req.setEndpoint(FIRMA_BASE + '/signing-requests/create-and-send');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(body));

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() >= 300) {
            throw new CalloutException('Firma error: ' + res.getBody());
        }

        Map<String, Object> data =
            (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        return (String) data.get('id');
    }
}
The callout:Firma_API prefix tells Salesforce to use the Named Credential, which injects the Authorization header automatically. No API key appears in code, in logs, or in serialized request inspection.
The create-and-send endpoint creates the signing request and emails it to recipients in one call. If you need an Apex-controlled review step, use POST /signing-requests to create a draft, then POST /signing-requests/{id}/send separately.
Expose the Apex method as an Invocable Action so Flow can call it:
public with sharing class FirmaInvocable {

    public class Input {
        @InvocableVariable(required=true) public String templateId;
        @InvocableVariable(required=true) public String firstName;
        @InvocableVariable(required=true) public String lastName;
        @InvocableVariable(required=true) public String email;
    }

    public class Output {
        @InvocableVariable public String signingRequestId;
    }

    @InvocableMethod(label='Send Firma signing request')
    public static List<Output> send(List<Input> inputs) {
        List<Output> results = new List<Output>();
        for (Input i : inputs) {
            FirmaService.Recipient r = new FirmaService.Recipient();
            r.first_name = i.firstName;
            r.last_name = i.lastName;
            r.email = i.email;
            String id = FirmaService.createAndSend(i.templateId, r);
            Output o = new Output();
            o.signingRequestId = id;
            results.add(o);
        }
        return results;
    }
}

Path 3: Agentforce action

To let an Agentforce agent send signing requests in response to a prompt (“send the standard NDA to alice@example.com”), wrap one of the above as an Agent Action.
  1. In Setup, open Agentforce Builder and pick the agent you want to extend
  2. Open the relevant Topic (e.g., Contract Lifecycle)
  3. Click New Action and choose Flow or Apex
    • Flow: pick a screen Flow or autolaunched Flow that wraps the Firma.Create and send signing request External Services action
    • Apex: pick the FirmaInvocable.send method
  4. Define inputs the agent will collect from the conversation: template, signer name, signer email
  5. Add the action to the Topic and publish
Use Agentforce’s confirmation prompt before any send action so the agent reads back the template name and signer details before triggering Firma. Outbound signing requests reach external parties; a confirmation step is worth the extra turn.

Webhook integration: react when documents are signed

Receive Firma webhooks into Salesforce by exposing an Apex REST endpoint, then update the originating record (Opportunity, Contract, custom object) when the signing request completes.

Step 1: Create the Apex REST endpoint

@RestResource(urlMapping='/firma-webhook')
global with sharing class FirmaWebhook {

    @HttpPost
    global static void receive() {
        RestRequest req = RestContext.request;
        Map<String, Object> payload =
            (Map<String, Object>) JSON.deserializeUntyped(
                req.requestBody.toString()
            );

        String eventType = (String) payload.get('type');
        Map<String, Object> data =
            (Map<String, Object>) payload.get('data');

        if (eventType == 'signing_request.completed') {
            Map<String, Object> sr =
                (Map<String, Object>) data.get('signing_request');
            String signingRequestId = (String) sr.get('id');

            List<Opportunity> opps = [
                SELECT Id FROM Opportunity
                WHERE Firma_Signing_Request_Id__c = :signingRequestId
                LIMIT 1
            ];
            if (!opps.isEmpty()) {
                opps[0].StageName = 'Signed';
                update opps[0];
            }
        }

        RestContext.response.statusCode = 200;
    }
}

Step 2: Verify the webhook signature

Firma signs every webhook payload with HMAC-SHA256. Always verify the signature in production to reject spoofed payloads. Your webhook secret is available in Settings > Webhooks in the Firma dashboard.
private static Boolean verifySignature(
    String payload, String signatureHeader, String secret
) {
    Blob hmac = Crypto.generateMac(
        'HmacSHA256',
        Blob.valueOf(payload),
        Blob.valueOf(secret)
    );
    String expected = EncodingUtil.convertToHex(hmac);
    return expected.equalsIgnoreCase(signatureHeader);
}
Call this at the top of the receive() method before processing:
String signature = RestContext.request.headers.get('x-firma-signature');
String body = RestContext.request.requestBody.toString();
String secret = '{!$Credential.Firma_Webhook.Secret}'; // or a Custom Metadata value

if (!verifySignature(body, signature, secret)) {
    RestContext.response.statusCode = 401;
    return;
}

Step 3: Expose the endpoint publicly

Firma’s webhook calls aren’t authenticated as a Salesforce user, so expose the endpoint through an Experience Cloud Site. Create a site (or use an existing one), then grant the site’s guest user profile permission to execute the FirmaWebhook Apex class. See Salesforce: Allow Guest Users to Access Apex REST for the full setup steps.

Step 4: Register the webhook in Firma

  1. In the Firma dashboard, go to Settings > Webhooks
  2. Add a new webhook pointing at https://<your-site-domain>/services/apexrest/firma-webhook
  3. Subscribe to the events you care about (signing_request.completed, signing_request.recipient.declined, signing_request.expired)
See the webhooks guide for all event types and payload shapes.

Embedded signing in Lightning

If you want signers to complete documents inside a Lightning page, use a Lightning Web Component that embeds the Firma signing UI. After a successful create-and-send call, read the signer ID from the response and pass it to the LWC:
<template>
  <iframe
    src={signingUrl}
    style="width:100%;height:900px;border:0;"
    allow="camera;microphone;clipboard-write"
    title="Document Signing"
  ></iframe>
</template>
import { LightningElement, api } from 'lwc';

export default class FirmaSigning extends LightningElement {
  @api signingRequestUserId;

  get signingUrl() {
    return `https://app.firma.dev/signing/${this.signingRequestUserId}`;
  }
}
Add https://app.firma.dev to your CSP Trusted Sites so the iframe loads. See the embedded signing guide for full setup including security best practices.

For ISVs and AppExchange partners: multi-org signing

If you’re building an AppExchange app that needs e-signatures for each of your customer orgs, use Firma Customer Workspaces. Each customer org gets its own isolated Firma workspace with separate templates, usage tracking, and audit trail. Provision a workspace per org at install time, store the workspace API key in a Protected Custom Setting on that org, and reference it from the Named Credential. No data bleed between customers.

MCP connection for AI-assisted building

Firma offers a Docs MCP server at https://docs.firma.dev/mcp. Connect it to Claude, Cursor, or any MCP-aware AI tool while you’re writing Apex or building Flows, and the assistant can answer accurate API questions from Firma’s docs. This is for the build experience and does not affect your deployed integration.

Next steps