# Get billing data Source: https://docs.formabledocs.com/api-reference/billing/get-billing-data /api-reference/openapi.json get /billing Returns the number of redlining sessions for client # Create a redline request Source: https://docs.formabledocs.com/api-reference/endpoint/create-redline-request POST /redline-requests Creates a new redline request for a template with specified members. Each member must have an email, displayName, and role. The template must have a DOCX source file available for redlining. # Create a redline URL Source: https://docs.formabledocs.com/api-reference/endpoint/create-redline-url POST /redline-requests/{redlineRequestId}/url Generates a redline URL for a specific member email associated with a redline request. The member must already be part of the redline request. API-created URLs use the embedded path. If it is not that member's turn, an out-of-turn URL is returned instead. # Create a regular signature request Source: https://docs.formabledocs.com/api-reference/endpoint/create-regular-signature-request POST /signature-requests Creates a signature request for a template with one or more signers and emails each signer a signing link (same delivery path as sharing from the Formable platform). If template fields have assigned roles, `signers` must include a signer for each of those roles. Requires `signers` (array of email, name, and role, in signing order). Optional `sender` (email and name); when omitted, sender defaults to the API key organization owner. Optionally accepts a `fields` array to pre-fill template fields. You do not need to create signing URLs; Formable emails each signer. # Create an embedded signature request Source: https://docs.formabledocs.com/api-reference/endpoint/create-signature-request POST /signature-requests/embedded Creates an embedded signature request for a template with one or more signers. If template fields have assigned roles, `signers` must include a signer for each of those roles. Requires `signers` (array of email, name, and role, in signing order). Optional `sender` (email and name); when omitted, sender defaults to the API key organization owner. Optionally accepts a `fields` array to pre-fill template fields. Use each signer's `recipientSignatureId` with the create signing URL endpoint to mint embeddable links. # Create a signing URL Source: https://docs.formabledocs.com/api-reference/endpoint/create-signing-url POST /recipient-signatures/{recipientSignatureId}/url Generates a signing URL for a specific recipient on a signature request. Use the `recipientSignatureId` returned when creating the signature request. The URL can be used to access the signing interface for that recipient. # Create a template Source: https://docs.formabledocs.com/api-reference/endpoint/create-template POST /templates Upload a PDF, DOC, or DOCX file to create a new template. The file must be under 40MB. `signer_roles` is optional. The playground is prefilled with `[{"name":"Client","order":0},{"name":"Witness","order":1}]`. Clear the field if you do not want roles. # Create a template edit URL Source: https://docs.formabledocs.com/api-reference/endpoint/create-template-edit-url POST /templates/{templateId}/edit-url Generates an edit URL for a template. The URL can be used to access the template editor interface. The client must be authorized to edit the template. # Get billing data Source: https://docs.formabledocs.com/api-reference/endpoint/get-billing GET /billing Returns the number of redlining sessions for client # Get a redline request Source: https://docs.formabledocs.com/api-reference/endpoint/get-redline-request GET /redline-requests/{redlineRequestId} Retrieves the redline request by ID # Get redline request events Source: https://docs.formabledocs.com/api-reference/endpoint/get-redline-request-events GET /redline-requests/{redlineRequestId}/events Retrieves the list of events for a redline request. Requires JWT authentication. # Get a signature request Source: https://docs.formabledocs.com/api-reference/endpoint/get-signature-request GET /signature-requests/{signatureRequestId} Retrieves the signature request by ID. The response matches create: a `signers` array (each with `recipientSignatureId`) and envelope `fields` with current values. Signature image data is never returned; signature fields use `value: null` and include `signedAt` when signed. Use `status` to poll progress, `signers` to mint signing URLs, and `fields` to see what has been filled. # Get signature request events Source: https://docs.formabledocs.com/api-reference/endpoint/get-signature-request-events GET /signature-requests/{signatureRequestId}/events Retrieves the list of events for a signature request. Requires JWT authentication. # Get a signed envelope Source: https://docs.formabledocs.com/api-reference/endpoint/get-signed-envelope GET /signature-requests/{signatureRequestId}/signed-envelope Retrieves a presigned URL for the signed envelope associated with a signature request. The envelope must be in Completed status. # Health check Source: https://docs.formabledocs.com/api-reference/endpoint/health-check GET /health Returns the health status of the API server, including uptime and version information. This endpoint does not require authentication. # List redline requests Source: https://docs.formabledocs.com/api-reference/endpoint/list-redline-requests GET /redline-requests Returns all redline requests for the authenticated client's organization, newest updated first. Optionally pass updatedSince to return only rows with updated_at strictly after that instant (ISO-8601). # List signature requests Source: https://docs.formabledocs.com/api-reference/endpoint/list-signature-requests GET /signature-requests Returns all signature requests for the authenticated client's organization, newest first. Optionally pass updatedSince to return only rows with updated_at after that instant (ISO-8601). # Update redline members Source: https://docs.formabledocs.com/api-reference/endpoint/put-redline-members PUT /redline-requests/{redlineRequestId}/members Replaces or upserts the members for an existing redline request. Each member must have an email, displayName, and role. Valid roles are DisclosingParty, ReceivingParty, DisclosingCounsel, ReceivingCounsel. # Health check endpoint Source: https://docs.formabledocs.com/api-reference/health/health-check-endpoint /api-reference/openapi.json get /health Returns the health status of the API server, including uptime and version information. This endpoint does not require authentication. # API reference Source: https://docs.formabledocs.com/api-reference/introduction REST endpoints for templates, signature requests, and related workflows. The Formable API is organized around **templates** and **signature requests** for e-signing, with optional **redline request** endpoints for contract negotiation. It's a REST API with predictable, resource-oriented URLs, JSON request and response bodies, and standard HTTP status codes. Every endpoint page includes a playground. Add your bearer token and send real requests from the docs. ## Base URL All endpoints are served under `/v1`. ```text theme={null} https://api.formabledocs.com/v1 ``` ## Authentication Every endpoint except the health check requires a Bearer token in the `Authorization` header. Create an API key in [Settings](https://app.formabledocs.com/settings), then pass it on each request. See [Authentication](/authentication) for details. Prefer a typed client? Use an [official SDK](/sdks/overview) for Node.js, Python, PHP, Java, Go, Ruby, or .NET. ```bash theme={null} Authorization: Bearer fmbl_YOUR_API_KEY ``` ## Response format Successful responses return JSON with a `200` status. Errors return a JSON object with a single `error` string: ```json theme={null} { "error": "templateId is required" } ``` ## Status codes | Code | Meaning | | ----- | ---------------------------------------------------------------------------------------------------- | | `200` | Success. | | `400` | Bad request — missing or invalid parameters. | | `401` | Unauthorized — missing or invalid bearer token. | | `403` | Forbidden — the token is valid but not authorized for this resource. | | `404` | Not found — the referenced resource doesn't exist. | | `409` | Conflict — the resource isn't in a valid state (for example, signing an already-completed document). | | `413` | Payload too large — the uploaded file exceeds 40MB. | | `500` | Internal server error. | ## Resources Upload documents and generate editor URLs. Send documents for e-signature and retrieve signed results. Create and manage collaborative redline negotiations. Retrieve usage information. # Create a redline request Source: https://docs.formabledocs.com/api-reference/redlining/create-a-redline-request /api-reference/openapi.json post /redline-requests Creates a new redline request for a template with specified members. Each member must have an email, displayName, and role. The template must have a DOCX source file available for redlining. # Create a redline URL for a member Source: https://docs.formabledocs.com/api-reference/redlining/create-a-redline-url-for-a-member /api-reference/openapi.json post /redline-requests/{redlineRequestId}/url Generates a redline URL for a specific member email associated with a redline request. The member must already be part of the redline request. API-created URLs use the embedded path. If it is not that member's turn, an out-of-turn URL is returned instead. # Get a redline request Source: https://docs.formabledocs.com/api-reference/redlining/get-a-redline-request /api-reference/openapi.json get /redline-requests/{redlineRequestId} Retrieves the redline request by ID # Get redline request events Source: https://docs.formabledocs.com/api-reference/redlining/get-redline-request-events /api-reference/openapi.json get /redline-requests/{redlineRequestId}/events Retrieves the list of events for a redline request. Requires JWT authentication. # List redline requests Source: https://docs.formabledocs.com/api-reference/redlining/list-redline-requests /api-reference/openapi.json get /redline-requests Returns all redline requests for the authenticated client's organization, newest updated first. Optionally pass updatedSince to return only rows with updated_at strictly after that instant (ISO-8601). # Update redline request members Source: https://docs.formabledocs.com/api-reference/redlining/update-redline-request-members /api-reference/openapi.json put /redline-requests/{redlineRequestId}/members Replaces or upserts the members for an existing redline request. Each member must have an email, displayName, and role. Valid roles are DisclosingParty, ReceivingParty, DisclosingCounsel, ReceivingCounsel. # Create a regular signature request Source: https://docs.formabledocs.com/api-reference/signature-requests/create-a-regular-signature-request /api-reference/openapi.json post /signature-requests Creates a signature request for a template with one or more signers and emails each signer a signing link (same delivery path as sharing from the Formable platform). If template fields have assigned roles, `signers` must include a signer for each of those roles. Requires `signers` (array of email, name, and role, in signing order). Optional `sender` (email and name); when omitted, sender defaults to the API key organization owner. Optionally accepts a `fields` array to pre-fill template fields. You do not need to create signing URLs; Formable emails each signer. # Create a signing URL for a recipient signature Source: https://docs.formabledocs.com/api-reference/signature-requests/create-a-signing-url-for-a-recipient-signature /api-reference/openapi.json post /recipient-signatures/{recipientSignatureId}/url Generates a signing URL for a specific recipient on a signature request. Use the `recipientSignatureId` returned when creating the signature request. The URL can be used to access the signing interface for that recipient. # Create an embedded signature request Source: https://docs.formabledocs.com/api-reference/signature-requests/create-an-embedded-signature-request /api-reference/openapi.json post /signature-requests/embedded Creates an embedded signature request for a template with one or more signers. If template fields have assigned roles, `signers` must include a signer for each of those roles. Requires `signers` (array of email, name, and role, in signing order). Optional `sender` (email and name); when omitted, sender defaults to the API key organization owner. Optionally accepts a `fields` array to pre-fill template fields. Use each signer's `recipientSignatureId` with the create signing URL endpoint to mint embeddable links. # Get a signature request Source: https://docs.formabledocs.com/api-reference/signature-requests/get-a-signature-request /api-reference/openapi.json get /signature-requests/{signatureRequestId} Retrieves the signature request by ID. The response matches create: a `signers` array (each with `recipientSignatureId`) and envelope `fields` with current values. Signature image data is never returned; signature fields use `value: null` and include `signedAt` when signed. Use `status` to poll progress, `signers` to mint signing URLs, and `fields` to see what has been filled. # Get a signed envelope for a signature request Source: https://docs.formabledocs.com/api-reference/signature-requests/get-a-signed-envelope-for-a-signature-request /api-reference/openapi.json get /signature-requests/{signatureRequestId}/signed-envelope Retrieves a presigned URL for the signed envelope associated with a signature request. The envelope must be in Completed status. # Get signature request events Source: https://docs.formabledocs.com/api-reference/signature-requests/get-signature-request-events /api-reference/openapi.json get /signature-requests/{signatureRequestId}/events Retrieves the list of events for a signature request. Requires JWT authentication. # List signature requests Source: https://docs.formabledocs.com/api-reference/signature-requests/list-signature-requests /api-reference/openapi.json get /signature-requests Returns all signature requests for the authenticated client's organization, newest first. Optionally pass updatedSince to return only rows with updated_at after that instant (ISO-8601). # Create a template edit URL Source: https://docs.formabledocs.com/api-reference/templates/create-a-template-edit-url /api-reference/openapi.json post /templates/{templateId}/edit-url Generates an edit URL for a template. The URL can be used to access the template editor interface. The client must be authorized to edit the template. # Create a template from a file upload Source: https://docs.formabledocs.com/api-reference/templates/create-a-template-from-a-file-upload /api-reference/openapi.json post /templates Upload a PDF, DOC, or DOCX file to create a new template. The file must be under 40MB. # Authentication Source: https://docs.formabledocs.com/authentication Authenticate API requests with an API key from Settings. The Formable API authenticates requests with a Bearer token in the `Authorization` header. Create an API key in [Settings](https://app.formabledocs.com/settings), then send it on every request. ```bash theme={null} Authorization: Bearer fmbl_YOUR_API_KEY ``` ## Get an API key API keys belong to an organization. Create one in the Formable app, then copy the key when it's shown. For detailed steps, see [Set up a developer account](/developer-account). Go to [Settings](https://app.formabledocs.com/settings) and create an organization if you don't have one yet. You need an organization before you can generate keys or register webhooks. In the **API keys** section, click **Generate key**, give it a name, and confirm. Copy the full key immediately and store it as an environment secret. Formable shows it **once** — after you close the dialog, only a short prefix (for example `fmbl_a1b2c3d4...`) remains visible. Never expose API keys in browser or mobile code. Call the API from your backend, and only pass short-lived signing and redline URLs to the client. You can rename or delete keys from the same Settings page. Deleting a key revokes it immediately. Each account can have up to 10 active keys. ## Use the key Include the key in the `Authorization` header of every API request. ```bash cURL theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests \ --header 'Authorization: Bearer fmbl_YOUR_API_KEY' ``` ```javascript Node.js theme={null} const res = await fetch( "https://api.formabledocs.com/v1/signature-requests", { headers: { Authorization: `Bearer ${process.env.FORMABLE_API_KEY}`, }, }, ); ``` ```python Python theme={null} import os import requests res = requests.get( "https://api.formabledocs.com/v1/signature-requests", headers={"Authorization": f"Bearer {os.environ['FORMABLE_API_KEY']}"}, ) ``` ```php PHP theme={null} $ch = curl_init('https://api.formabledocs.com/v1/signature-requests'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer '.getenv('FORMABLE_API_KEY'), ], ]); $res = curl_exec($ch); ``` ```java Java theme={null} HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.formabledocs.com/v1/signature-requests")) .header("Authorization", "Bearer " + System.getenv("FORMABLE_API_KEY")) .GET() .build(); HttpResponse res = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); ``` ```go Go theme={null} req, _ := http.NewRequest( http.MethodGet, "https://api.formabledocs.com/v1/signature-requests", nil, ) req.Header.Set("Authorization", "Bearer "+os.Getenv("FORMABLE_API_KEY")) res, err := http.DefaultClient.Do(req) ``` ```ruby Ruby theme={null} require "net/http" uri = URI("https://api.formabledocs.com/v1/signature-requests") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer #{ENV.fetch("FORMABLE_API_KEY")}" res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end ``` ```csharp C# theme={null} using var client = new HttpClient(); client.DefaultRequestHeaders.Add( "Authorization", "Bearer " + Environment.GetEnvironmentVariable("FORMABLE_API_KEY")); var res = await client.GetAsync("https://api.formabledocs.com/v1/signature-requests"); ``` The [Node](/sdks/node), [Python](/sdks/python), [PHP](/sdks/php), [Java](/sdks/java), [Go](/sdks/go), [Ruby](/sdks/ruby), and [.NET](/sdks/dotnet) SDKs send this header for you. Keys start with `fmbl_`. Treat them like passwords: store them in a secrets manager, rotate by creating a new key and deleting the old one, and never commit them to source control. ## Base URL ```text theme={null} https://api.formabledocs.com/v1 ``` All endpoints are served under this base URL, so a full path looks like `https://api.formabledocs.com/v1/signature-requests`. ## Authorization errors Requests fail with a `401` when the bearer token is missing, malformed, revoked, or expired: ```json theme={null} { "error": "Auth token not valid" } ``` A `403` means the token is valid but your organization isn't authorized for the resource — for example, creating an edit URL for a template that belongs to another organization. # Core concepts Source: https://docs.formabledocs.com/concepts The key objects and terms you'll work with when embedding e-signatures. For e-signatures, you upload a reusable **template**, create a **signature request** with one or more signers, mint a **signing URL** per recipient for embedded flows, then download the **signed envelope** when they're done. ## Templates A **template** is a document you've uploaded (PDF, DOC, or DOCX) with fields placed on it — for example signature, text, date, checkbox, or amount. Templates are reusable: upload once, then create many signature requests from the same `templateId`. Templates can define **signer roles** (for example `Client` and `Witness`). Assign a role to each required field in the editor. When you create a signature request, every role used by a required field must have a matching signer. * Create a template with [Create a template](/api-reference/endpoint/create-template). * Place fields using the **editor URL** returned on creation, or fetch a fresh one with [Create a template edit URL](/api-reference/endpoint/create-template-edit-url). See the [Embedded templates walkthrough](/walkthroughs/embedded-templates) for the full workflow. ## Signature requests A **signature request** is one send of a template to one or more signers. You can create a [regular (email) signature request](/api-reference/endpoint/create-regular-signature-request) or an [embedded signature request](/api-reference/endpoint/create-signature-request). Both return a `signatureRequestId` plus a `recipientSignatureId` for each signer. | Field | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `signers` | People who sign (`email`, `name`, and `role` matching required field roles). Returned on create and [get](/api-reference/endpoint/get-signature-request). | | `recipientSignatureId` | Per-signer ID on each signer. For embedded flows, use it to mint that person's signing URL. | | `sender` | Optional. Who is sending (`email` + `name`). Defaults to the API key organization owner. | | `status` | `Created` when made, `Completed` once signed, or `Expired`. | | `fields` | Envelope fields on create and get: type, required, filled, current value, and which recipient owns the field. Signature image data is never returned. | | `testMode` | When `true`, the request doesn't count toward billing. Documents are watermarked and not legally binding. | For **regular** requests, Formable emails each signer a link — you do not create signing URLs. For **embedded** requests, generate a short-lived **signing URL** from each `recipientSignatureId` and embed it in an iframe. When signing finishes, wait for the [`document_completed`](/webhooks/events#document_completed) webhook, then download the **signed envelope** — a temporary URL to the completed PDF. [Get a signature request](/api-reference/endpoint/get-signature-request) returns the same `signers` (with `recipientSignatureId`) and `fields` as create, plus the current `status`. Use it to poll progress or recover recipient IDs you didn't persist. An **envelope** is Formable's internal record of a document prepared for a recipient. As an API consumer you work with signature requests; the signed envelope endpoint is how you get the final PDF. ## Embedded Signing URLs are short-lived and embeddable Signing URLs are meant to be **embedded in an iframe** in your own product. They expire (typically after one hour), so generate them on demand right before you display them rather than storing them. Generate signing URLs from your backend and pass them to the client just-in-time. Never expose your API keys in client-side code. ## Test mode Signature requests accept a `testMode` flag. Use it while integrating so activity doesn't count toward billing. Test mode documents are watermarked and are not legally binding. Test API keys always created test signatures. ## Events Track signing progress two ways: * **Push** — configure a [webhook](/webhooks/overview) for real-time events such as `document_viewed`, `document_signed`, and `document_completed`. Wait for `document_completed` before downloading the signed PDF. * **Pull** — poll [signature request events](/api-reference/endpoint/get-signature-request-events) or check the signature request `status` to reconcile state. Prefer webhooks for reacting in near real time, and use polling to backfill anything missed. ## Redlining (optional) [Redlining](/walkthroughs/redlining) is a separate workflow for negotiating a DOCX contract between two parties before signing. It uses **redline requests**, member roles, and turn-based rounds — see that walkthrough when you're ready. # Set up a developer account Source: https://docs.formabledocs.com/developer-account Create a Formable account, organization, API keys, and webhooks so you can call the API: all in less than 5 minutes. To use the Formable API, create an account, add an organization, then generate an API key. Optionally register a webhook to receive signing and redlining events. ## Create an account 1. Go to [app.formabledocs.com/signup](https://app.formabledocs.com/signup) and create an account. 2. Sign in at [app.formabledocs.com](https://app.formabledocs.com). 3. Open [Settings](https://app.formabledocs.com/settings). Until you create an organization, API keys and webhooks stay locked. Settings page showing an empty Organization section and disabled API keys and Webhooks actions ## Create an organization API keys, webhooks, and API resources belong to an organization. Each account can have one organization. In the **Organization** section, click **Create organization**. Enter a name and click **Create organization**. Create organization modal with Name set to Acme Inc Your organization name and ID appear in Settings. **Generate key** and **Register webhook** become available. Settings page showing Acme Inc organization with Generate key and Register webhook enabled ## Generate an API key Use an API key as a Bearer token on every request. See [Authentication](/authentication) for how to send it. In the **API keys** section, click **Generate key**, enter a name, and confirm. Generate API key modal with Name set to TestKeyName Formable shows the full key once. Copy it and store it before you click **Done**. API key created modal showing a full fmbl_ key with a copy button Never expose API keys in browser or mobile code. Call the API from your backend. You can rename or delete keys from the same page. Deleting a key revokes it immediately. Each account can have up to 10 active keys. ## Register a webhook Webhooks notify your app when documents are viewed, signed, or updated in a redline workflow. Each organization has one webhook URL. See [Webhooks overview](/webhooks/overview) for delivery details. In the **Webhooks** section, click **Register webhook**, enter an HTTPS callback URL, and confirm. Register webhook modal with a Callback URL field Copy the signing secret when it's shown and store it as an environment secret. You'll use it to [verify webhooks](/webhooks/verifying-webhooks). Formable does not show the secret again. You may rotate secrets if need be. Webhook registered modal showing a signing secret with a copy button Production callback URLs must use `https://`. After registration you can edit the URL, rotate the secret, or delete the endpoint from the actions menu next to your webhook. ## Next steps Send your API key on every request. Create a signature request and get a signing URL. Typed clients for Node.js, Python, PHP, Java, Go, Ruby, and .NET. Handle delivery, retries, and event payloads. Learn how templates and signature requests fit together. # Formable API Source: https://docs.formabledocs.com/index Collect e-signatures with a REST API. Embed signing in your product, or send documents to be signed by email. The Formable API lets you collect signatures without building the signing experience from scratch. Upload a document once as a **template**, send it as a **signature request**, and get it signed: either inside your product, or by email. You can try the API without writing code. Open the [API Reference](/api-reference/introduction), add your bearer token on any endpoint page, and send real requests from the docs playground. Building in Node.js, Python, PHP, Java, Go, Ruby, or .NET? Start with the [official SDKs](/sdks/overview). Create an account, organization, API keys, and webhooks in Settings. Send your first signature request end to end. Learn how templates, signature requests, and envelopes fit together. Typed clients for Node.js, Python, PHP, Java, Go, Ruby, and .NET. Separate guides for embedded signing and embedded templates. Prefer a white-glove implementation? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle integrating e-signing in your application for you. ## What you can build * **Embedded e-signature** — Host signing inside your product via iframe. See [Embedded signing](/walkthroughs/embedded-signing). * **Templates** — Upload a document and place fields in the editor for your signers to fill. ## How it works Upload a PDF, DOC, or DOCX with [Create a template](/api-reference/endpoint/create-template). Formable returns a `templateId` you reuse for every send. Start an [embedded signature request](/api-reference/endpoint/create-signature-request) from the template with `signers`, then generate a [signing URL](/api-reference/endpoint/create-signing-url) for each recipient to embed in your product. When signing finishes, wait for the [`document_completed`](/webhooks/events#document_completed) webhook, then fetch the [signed envelope](/api-reference/endpoint/get-signed-envelope). Set up your developer account, then follow the [quickstart](/quickstart) to send your first signature request. ## Also available: redlining If you need turn-based contract negotiation before signing, Formable also offers an [embedded redlining](/walkthroughs/redlining) walkthrough. # Adobe Acrobat Sign → Embedded signing Source: https://docs.formabledocs.com/migrations/adobe-acrobat-sign/embedded-signing Migrate Adobe Acrobat Sign agreements and signing URLs to the Formable API. This guide helps you move an Adobe Acrobat Sign embedded signing integration to Formable. You still create a send, mint a signing URL, embed it, and download the completed PDF. You drop OAuth, regional hosts, transient documents on every send, and `OUT_FOR_SIGNATURE` polling in favor of Bearer API keys, template-first sends, and webhook- or poll-driven completion. If you also prepare library documents or authoring views inside your product, migrate that first with [Adobe Acrobat Sign → Embedded templates](/migrations/adobe-acrobat-sign/embedded-templates). Formable signing always starts from a prepared template. New to Formable? Complete the [Embedded signing walkthrough](/walkthroughs/embedded-signing) first, then use this page as a translation layer from Acrobat Sign agreements and signing URLs. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Adobe Acrobat Sign integration to the Formable API. ## What you're moving In Adobe Acrobat Sign you typically: 1. Authenticate with OAuth (and often `x-api-user` / on-behalf-of headers) against a regional host such as `api.na1.adobesign.com` 2. Upload a transient document, or reference a library document 3. `POST /agreements` with `participantSetsInfo` and `state: "IN_PROCESS"` 4. Poll until the agreement is `OUT_FOR_SIGNATURE`, then call `GET /agreements/{agreementId}/signingUrls` (or `POST .../views` with a `SIGNING` view) 5. Embed or redirect to the signing URL, detect completion via webhooks or status polling, then download the combined PDF In Formable you: 1. Authenticate with a Bearer API key (`fmbl_…`) 2. Create an **embedded signature request** from a `templateId` with a `signers` array (each with `email`, `name`, and `role`) 3. Mint a `signingUrl` from each signer's `recipientSignatureId` (no transient document, no regional host, no status wait) 4. Load it in a plain iframe (URL lasts **1 hour**) 5. Handle [`document_completed`](/webhooks/events#document_completed) or poll `status`, then download the signed envelope The product journey stays the same: prepare a document, send it, embed signing, react when done. The API surface is smaller and organization-scoped by the key. ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). You do not need OAuth token exchange for the standard integration. Re-upload documents and place fields in Formable. If you embed library authoring, follow [Embedded templates migration](/migrations/adobe-acrobat-sign/embedded-templates). Map participants and merge fields to `signers`, `sender`, and optional `fields`. Call create signing URL; drop regional hosts and `OUT_FOR_SIGNATURE` polling. Register one org webhook URL and verify `Content-Sha256`. See [Webhooks](/webhooks/overview). ## What's similar Several Acrobat Sign ideas transfer directly: * A reusable **library document / template** still backs many sends. * You still create a send unit (agreement → **signature request**), then open a short-lived URL for the signer. * Prefill / merge values still happen at send time; Formable uses `fields: [{ fieldId, value }]`. * Sandbox / non-production traffic maps to `testMode: true`. Test mode documents are watermarked and are not legally binding. * After completion you still fetch a combined signed PDF. If your Acrobat Sign flow is "create agreement → get signing URL → iframe → webhook → download," you are remapping objects and removing transient-document and regional-host bookkeeping, not redesigning the user journey. ## What's different These Acrobat Sign-specific pieces do not carry over: | Area | Adobe Acrobat Sign | Formable | | ------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- | | Auth | OAuth access token; often `x-api-user` | Bearer `fmbl_…`; no acting-as headers | | Base URL | Regional (`api.na1.adobesign.com`, etc.) | `https://api.formabledocs.com` | | Files on send | Transient document or library document on create | Upload a [template](/walkthroughs/embedded-templates) first, then send with `templateId` | | Ready-to-sign | Poll until `OUT_FOR_SIGNATURE` before signing URLs | Create the request, then mint the URL when ready | | Signing URL | `GET .../signingUrls` or `POST .../views` (`SIGNING`) | [`POST /v1/recipient-signatures/{id}/url`](/api-reference/endpoint/create-signing-url) | | Completion UX | Redirect / return URL options on some views | `onSigningComplete` iframe message for UI; confirm with webhooks or polling | | View TTL | Short-lived signing URLs | **1 hour** | | Webhooks | Acrobat Sign webhook subscriptions + event names | Org-level URL, `Content-Sha256` | | Participants | `participantSetsInfo` with `order` / `role` | `signers[]` with `email`, `name`, and `role` | You no longer upload a transient document per send or wait for Acrobat Sign's async agreement state machine. Create the request when you're ready to send, mint the URL when the user is ready to sign, and listen for completion out of band. ## Concept map | Adobe Acrobat Sign | Formable | Notes | | ---------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------- | | OAuth access token | API key (`Authorization: Bearer fmbl_…`) | No OAuth for the standard integration. | | Account / `x-api-user` | Organization | Implied by the API key. | | Agreement | Signature request | Formable's send unit. | | Participant set member | `signers[]` with `email`, `name`, and `role` | Embedded by default once you mint a `signingUrl`. Save each `recipientSignatureId`. | | Library document | Template | Prepared once; reused via `templateId`. | | Transient document | Template upload | Do not re-upload on every send. | | Signing URL | `signingUrl` | Expires after **1 hour**. | | `AGREEMENT_WORKFLOW_COMPLETED` / similar | `document_completed` / status `Completed` | Then fetch the signed PDF. | | Combined document | Signed envelope | Presigned PDF download URL. | ## Endpoint map | Adobe Acrobat Sign | Formable | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `POST /api/rest/v6/transientDocuments` + `POST /agreements` | Upload a [template](/walkthroughs/embedded-templates), then [`POST /v1/signature-requests/embedded`](/api-reference/endpoint/create-signature-request) | | `POST /agreements` with `libraryDocumentId` | [`POST /v1/signature-requests/embedded`](/api-reference/endpoint/create-signature-request) with `templateId` | | `GET /agreements/{id}/signingUrls` | [`POST /v1/recipient-signatures/{id}/url`](/api-reference/endpoint/create-signing-url) | | `POST /agreements/{id}/views` (`SIGNING`) | [`POST /v1/recipient-signatures/{id}/url`](/api-reference/endpoint/create-signing-url) | | `GET /agreements/{id}/combinedDocument` | [`GET /v1/signature-requests/{id}/signed-envelope`](/api-reference/endpoint/get-signed-envelope) | | Webhook subscriptions | Webhook URL in [Settings](https://app.formabledocs.com/settings) | ## Auth Acrobat Sign: ```bash theme={null} Authorization: Bearer {access_token} ``` against a regional host such as `https://api.na1.adobesign.com/api/rest/v6/...`, often with `x-api-user`. Formable: ```bash theme={null} curl https://api.formabledocs.com/v1/signature-requests \ --header "Authorization: Bearer fmbl_YOUR_API_KEY" ``` One global API host. No `x-api-user`. Create the key in [Settings](https://app.formabledocs.com/settings) and keep it on the server. Full details: [Authentication](/authentication). ## Migrate the flow ### 1. Create the signature request (was: create agreement) **Adobe Acrobat Sign** (simplified, from a library document): ```json theme={null} { "fileInfos": [ { "libraryDocumentId": "ADOBE_LIBRARY_DOCUMENT_ID" } ], "name": "Customer Agreement", "participantSetsInfo": [ { "memberInfos": [{ "email": "jane@example.com" }], "order": 1, "role": "SIGNER" } ], "signatureType": "ESIGN", "state": "IN_PROCESS", "mergeFieldInfo": [ { "fieldName": "company", "defaultValue": "Acme Corporation" } ] } ``` **Formable:** ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/signature-requests/embedded \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "role": "SIGNER" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "fields": [ { "fieldId": "field_company_name", "value": "Acme Corporation" } ], "testMode": true }' ``` ```json Response theme={null} { "signatureRequestId": "sr_456def", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "recipientSignatureId": "rsig_789ghi" } ] } ``` Pass a `signers` array on every create. Each signer needs `email`, `name`, and `role`. `sender` is optional and defaults to the org owner. Map merge field names to Formable `fieldId`s from the template editor (not Acrobat Sign `fieldName`s). Save each signer's `recipientSignatureId` to mint their signing URL. | Adobe Acrobat Sign | Formable | | ------------------------------------------- | ---------------------------------------------------- | | `libraryDocumentId` | `templateId` | | `transientDocumentId` on create | Upload a template first; do not attach files on send | | `participantSetsInfo[].memberInfos[].email` | `signers[].email` / `name` | | `order` / `role` | `signers[].role` (signing order follows the array) | | `mergeFieldInfo` | `fields: [{ fieldId, value }]` | | `state: "IN_PROCESS"` | Create, then mint the URL when ready | | Sandbox / non-billing testing | `testMode: true` (watermarked, not legally binding) | ### 2. Create the signing URL (was: signingUrls / SIGNING view) **Adobe Acrobat Sign:** ```bash theme={null} GET /api/rest/v6/agreements/{agreementId}/signingUrls ``` Often after polling `GET /agreements/{agreementId}` until status is `OUT_FOR_SIGNATURE`. **Formable:** ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/recipient-signatures/rsig_789ghi/url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signingUrl": "https://app.formabledocs.com/sign/embedded/xyz789abc", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` No regional host, no `OUT_FOR_SIGNATURE` wait, and no separate views payload. Formable URLs last **1 hour**. Creating a URL for an already completed request returns `409`. ### 3. Embed on the client ```html theme={null} ``` ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; if (event.data.type === "onSigningComplete") { // signer finished — close the iframe or show success } }); ``` Use `onSigningComplete` for client UX instead of relying only on a return / redirect URL. Confirm completion with [`document_completed`](/webhooks/events#document_completed) or by polling `status` until `Completed` before downloading. See [Embedded signing](/walkthroughs/embedded-signing#detect-completion). ### 4. Download the signed PDF ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` Webhook verification uses `Content-Sha256`. See [Verifying webhooks](/webhooks/verifying-webhooks) and [Webhooks overview](/webhooks/overview). ## Differences to plan for Drop OAuth token exchange and `x-api-user` headers for a Bearer API key on your server. The organization is implied by the key. Acrobat Sign often uploads a transient document per agreement. Formable expects a prepared [template](/walkthroughs/embedded-templates) first. Upload once, place fields, then send with `templateId`. You do not wait for an async agreement state before minting a signing URL. Create the signature request, then call create signing URL when the signer is ready. Drop regional base URL discovery (`api.na1`, `api.eu1`, and similar). All Formable calls go to `https://api.formabledocs.com`. Listen for `onSigningComplete` to update the client immediately. Confirm with a webhook or poll before downloading, then build any "signing finished" screen in your own app. ## Checklist In [Settings](https://app.formabledocs.com/settings). Or migrate the [embedded template flow](/migrations/adobe-acrobat-sign/embedded-templates) separately. Use create embedded signature request with `signers` (including `role`), optional `sender`, and optional `fields`. Call create signing URL with each signer's `recipientSignatureId`; drop regional hosts and `OUT_FOR_SIGNATURE` polling. Handle `document_completed` and download the signed envelope. ## Next steps Map library documents and authoring views to Formable. Full Formable signing walkthrough. Register endpoints and understand delivery. Bearer `fmbl_` API keys. # Adobe Acrobat Sign → Embedded templates Source: https://docs.formabledocs.com/migrations/adobe-acrobat-sign/embedded-templates Migrate Adobe Acrobat Sign library documents and authoring views to Formable's upload and editUrl flow. This guide helps you move Adobe Acrobat Sign library document creation and embedded authoring to Formable. You still upload a document, open an editor for field placement, and reuse a template ID when sending. Formable collapses that into a multipart upload plus an `editUrl` iframe, without OAuth, regional hosts, or transient documents. When templates are ready, continue with [Adobe Acrobat Sign → Embedded signing](/migrations/adobe-acrobat-sign/embedded-signing) to send documents for signature. New to Formable? Complete the [Embedded templates walkthrough](/walkthroughs/embedded-templates) first, then use this page as a translation layer from Acrobat Sign library documents and authoring views. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Adobe Acrobat Sign integration to the Formable API. ## What you're moving In Adobe Acrobat Sign apps you typically: 1. `POST /transientDocuments` to upload the file 2. `POST /libraryDocuments` (or create an agreement in `AUTHORING` state) to prepare a reusable document 3. Optionally open an authoring / `EDIT` view (`POST /libraryDocuments/{id}/views` or agreement views) so users place form fields in your product 4. Send agreements from that library document ID, often with `mergeFieldInfo` In Formable you: 1. `POST /v1/templates` with the file 2. Embed `editUrl` so users place fields in your product 3. Copy each field's `id` for later prefills 4. Create signature requests with that `templateId` and a `signers` array You are migrating document preparation and in-product editing. Agreement send and signing URLs live on the paired [signing guide](/migrations/adobe-acrobat-sign/embedded-signing). ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). Call [`POST /v1/templates`](/api-reference/endpoint/create-template) with PDF or Word files (max 40MB). Embed `editUrl` from create, or [`POST .../edit-url`](/api-reference/endpoint/create-template-edit-url) when it expires. Place fields in the Formable editor and update prefill code to use `fieldId` instead of Acrobat Sign form / merge field names. Continue with [embedded signing migration](/migrations/adobe-acrobat-sign/embedded-signing). ## What's similar The high-level template model still matches Acrobat Sign: * Upload once, get a reusable template ID, send many times. * Users can edit field placement inside your product via a short-lived editor URL. * Prefills happen at send time against named field identifiers. * Template editing and signing use the same auth story on Formable (one Bearer key for both). If your Acrobat Sign product already embeds an authoring view and stores a library document ID for later agreement creates, keep that product flow. Rebuild how the file is uploaded and how the editor URL is minted. ## What's different Plan for these Acrobat Sign-specific patterns that do not map 1:1: | Area | Adobe Acrobat Sign | Formable | | --------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | Auth | OAuth + regional hosts; often `x-api-user` | Bearer `fmbl_…`; organization from the key | | Upload path | Transient document, then library document or AUTHORING agreement | One multipart `POST /templates` | | Field placement | Authoring UI, form field generators, or `PUT .../formFields` | Visual placement in Formable's editor | | Prefill keys | Form field / merge field names | `fieldId` from the Formable editor | | Edit view | Library `EDIT` view or agreement authoring view | `editTemplateAccess.editUrl` or `POST .../edit-url` | | Editor TTL | View expiration from Acrobat Sign | Default about **1 day**; mint just in time | | Roles on create | Participant sets / roles on library or agreement | Optional `signer_roles` on create; assign roles to fields in the editor; pass `signers[].role` at send time | Anchor text generators, absolute coordinates in form-field payloads, and per-send AUTHORING agreements do not carry over as the primary path. Recreate positions in the Formable editor, then store the new `fieldId`s in your database or config. ## Concept map | Adobe Acrobat Sign | Formable | Notes | | -------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------ | | Library document | Template | Upload once, reuse `templateId`. | | Transient document | File upload on create template | No separate transient ID to manage. | | Form fields (signature, text, date, …) | Fields (Signature, Text, Date, …) | Placed in Formable's visual editor. | | Library `EDIT` / authoring view URL | `editUrl` | Embed in an iframe. Expires (default **1 day**). | | Merge / form field names | `fieldId` | Prefill by ID at signature-request time. | | Participant roles on the library | Optional `signer_roles` on create + field placement in the editor | Pass matching `signers[].role` at send time. | ## Endpoint map | Adobe Acrobat Sign | Formable | | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | `POST /transientDocuments` + `POST /libraryDocuments` | [`POST /v1/templates`](/api-reference/endpoint/create-template) | | `POST /libraryDocuments/{id}/views` (`EDIT`) | [`POST /v1/templates/{templateId}/edit-url`](/api-reference/endpoint/create-template-edit-url) | | Agreement `AUTHORING` + authoring view | Formable template `editUrl` (prepare once, not per send) | | `editUrl` / view URL on create | `editTemplateAccess.editUrl` returned from create | ## Auth Acrobat Sign library APIs sit under regional `/api/rest/v6/...` hosts with an OAuth token. Formable uses the same Bearer key as signing: ```bash theme={null} curl https://api.formabledocs.com/v1/templates \ --header "Authorization: Bearer fmbl_YOUR_API_KEY" \ --form 'file=@./agreement.pdf' \ --form 'filename=agreement.pdf' ``` See [Authentication](/authentication). Call these endpoints from your backend only. ## Migrate the flow ### 1. Upload the template **Adobe Acrobat Sign** (simplified): ```bash theme={null} # 1) Upload transient document POST /api/rest/v6/transientDocuments # 2) Create library document from transientDocumentId POST /api/rest/v6/libraryDocuments ``` **Formable** — one call: ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates \ --header "Authorization: Bearer $TOKEN" \ --form 'file=@./agreement.pdf' \ --form 'filename=agreement.pdf' \ --form 'signer_roles=[{"name":"SIGNER","order":0}]' ``` ```json Response theme={null} { "templateId": "abc123xyz", "editTemplateAccess": { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } } ``` There is no separate transient document step. The organization comes from the API key. Supported uploads: PDF, DOCX, and DOC, up to **40MB**. `signer_roles` is optional — pass it as a JSON string in multipart form data when you want named roles before opening the editor. You still assign those roles to required fields in the editor. ### 2. Get a fresh editor URL When the create URL has expired, or whenever a user needs to edit again: ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates/abc123xyz/edit-url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` This replaces Acrobat Sign's library `EDIT` view (or agreement authoring view) call. Generate just-in-time on your backend. Unknown IDs return `404`; templates outside your organization return `403`. ### 3. Embed on the client ```html theme={null} ``` Pass only the `editUrl` to the browser — never the API key. Template editing uses the same iframe pattern as [embedded signing](/migrations/adobe-acrobat-sign/embedded-signing). Listen for `onTemplateEditorSaved` when the user finishes editing — see [Embedded templates](/walkthroughs/embedded-templates#listen-for-editor-events). ## Form field → field mapping | Adobe Acrobat Sign (typical) | Formable field | | ---------------------------- | -------------- | | Signature / signature block | Signature | | Text / full name / company | Text | | Date / date signed | Date | | Checkbox | Checkbox | | Numerical / amount-style | Amount | If you previously set `mergeFieldInfo` when creating an agreement, use Formable `fields: [{ fieldId, value }]` at [signature request](/migrations/adobe-acrobat-sign/embedded-signing) time instead. Field IDs come from the editor after placement — see [prefills](/walkthroughs/embedded-signing#prefill-fields). ## Differences to plan for Acrobat Sign separates transient upload from library document create. Formable returns a usable `templateId` and `editUrl` from a single multipart create. Anchor text generators and coordinate payloads don't carry over as the primary path. Place fields in Formable's editor and reference `fieldId`s for prefills. Some Acrobat Sign flows author fields on each agreement. Formable templates hold fields; you pass `signers` (with `role`) when creating the signature request. Template create and edit-url calls use the same Bearer API key as signing — no regional OAuth token or `x-api-user`. Acrobat Sign form / merge field names won't match Formable `fieldId`s. After users place fields, store the new IDs and update your send payload. ## Checklist In [Settings](https://app.formabledocs.com/settings). Call [`POST /v1/templates`](/api-reference/endpoint/create-template). Embed `editUrl` from create or [`POST .../edit-url`](/api-reference/endpoint/create-template-edit-url). Place fields in the editor and update prefill code. Continue with [embedded signing migration](/migrations/adobe-acrobat-sign/embedded-signing). ## Next steps Map agreements and signing URLs to Formable. Full Formable template editor walkthrough. How templates and signature requests fit together. Bearer `fmbl_` API keys. # Docusign → Embedded signing Source: https://docs.formabledocs.com/migrations/docusign/embedded-signing Migrate Docusign embedded signing (envelopes, recipient views) to the Formable API. This guide helps you move a Docusign embedded signing integration to Formable. You still create a send, mint a recipient signing URL, embed it, and download the completed PDF. You drop OAuth account paths, `clientUserId`, and `returnUrl` redirects in favor of Bearer API keys, a simpler create payload, and webhook- or poll-driven completion. If you also prepare templates inside your product, migrate that first with [Docusign → Embedded templates](/migrations/docusign/embedded-templates). Formable signing always starts from a prepared template. New to Formable? Complete the [Embedded signing walkthrough](/walkthroughs/embedded-signing) first, then use this page as a translation layer from Docusign envelopes and recipient views. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Docusign integration to the Formable API. ## What you're moving In Docusign you typically: 1. Authenticate with OAuth and call account-scoped REST paths 2. Create an envelope with a recipient that has a `clientUserId` (embedded / captive signer) 3. Call `EnvelopeViews:createRecipientView` with matching identity fields plus a `returnUrl` 4. Redirect or iframe the view URL (often \~5 minute TTL) 5. Detect completion via Connect / `eventNotification` or the `returnUrl` query event, then download documents In Formable you: 1. Authenticate with a Bearer API key (`fmbl_…`) 2. Create an **embedded signature request** from a `templateId` with a `signers` array (each with `email`, `name`, and `role`) 3. Mint a `signingUrl` from each signer's `recipientSignatureId` (no `clientUserId`, no `returnUrl`) 4. Load it in a plain iframe (URL lasts **1 hour**) 5. Handle [`document_completed`](/webhooks/events#document_completed) or poll `status`, then download the signed envelope The product journey stays the same: prepare a document, send it, embed signing, react when done. The API surface is smaller and organization-scoped by the key rather than by `accountId` in the path. ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). You do not need JWT or Authorization Code exchange for the standard integration. Re-upload documents and place fields in Formable. If you embed template editing, follow [Embedded templates migration](/migrations/docusign/embedded-templates). Map template roles and tab values to `signers`, `sender`, and optional `fields`. Call create signing URL; drop `clientUserId`, `returnUrl`, and `authenticationMethod`. Register one org webhook URL and verify `Content-Sha256`. See [Webhooks](/webhooks/overview). ## What's similar Several Docusign ideas transfer directly: * A reusable **template** still backs many sends. * You still create a send unit (envelope → **signature request**), then open a short-lived URL for the signer. * Prefill / tab values still happen at send time; Formable uses `fields: [{ fieldId, value }]`. * Demo / non-billing traffic maps to `testMode: true` (instead of relying only on a demo account). Test mode documents are watermarked and are not legally binding. * After completion you still fetch a combined signed PDF. If your Docusign flow is "create envelope from template → create recipient view → iframe → Connect → download," you are remapping objects and removing embedded-signer bookkeeping, not redesigning the user journey. ## What's different These Docusign-specific pieces do not carry over: | Area | Docusign | Formable | | --------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------- | | Auth | OAuth access token + `/accounts/{accountId}/...` | Bearer `fmbl_…`; no `accountId` in the path | | Embedded marker | Recipient `clientUserId` | Embedded by default once you mint a `signingUrl` | | View request | Match email / userName / `clientUserId`; require `returnUrl` | POST create URL with the signer's `recipientSignatureId` | | Completion UX | Often `returnUrl` redirect with an `event` query param | `onSigningComplete` iframe message for UI; confirm with webhooks or polling | | View TTL | Commonly \~5 minutes | **1 hour** | | Webhooks | Connect / `eventNotification`, `X-DocuSign-Signature-1` | Org-level URL, `Content-Sha256` | | Roles | `templateRoles` / `roleName` | `signers[].role` (match template field roles) | You no longer maintain a parallel "captive signer" identity. Create the request when you're ready to send, mint the URL when the user is ready to sign, and listen for completion out of band. ## Concept map | Docusign | Formable | Notes | | ----------------------------- | -------------------------------------------- | -------------------------------------------------------------------------- | | OAuth access token | API key (`Authorization: Bearer fmbl_…`) | No JWT / Auth Code for the standard integration. | | Account (`accountId`) | Organization | Implied by the API key. | | Envelope | Signature request | Formable's send unit. | | Recipient + `clientUserId` | `signers[]` with `email`, `name`, and `role` | Embedded by default — no `clientUserId`. Save each `recipientSignatureId`. | | Recipient view URL | `signingUrl` | Expires after **1 hour** (Docusign \~5 minutes). | | `returnUrl` | `onSigningComplete` `postMessage` | Close the iframe from the client event; confirm via webhooks or polling. | | Connect / `eventNotification` | Webhooks | Org-level URL + `Content-Sha256`. | | Envelope `completed` | `document_completed` / status `Completed` | Then fetch the signed PDF. | ## Endpoint map | Docusign | Formable | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `POST /v2.1/accounts/{accountId}/envelopes` | [`POST /v1/signature-requests/embedded`](/api-reference/endpoint/create-signature-request) | | `POST .../envelopes/{envelopeId}/views/recipient` | [`POST /v1/recipient-signatures/{id}/url`](/api-reference/endpoint/create-signing-url) | | Get document / combined PDF | [`GET /v1/signature-requests/{id}/signed-envelope`](/api-reference/endpoint/get-signed-envelope) | | Connect / `eventNotification` | Webhook URL in [Settings](https://app.formabledocs.com/settings) | ## Auth Docusign: ```bash theme={null} Authorization: Bearer {access_token} ``` against `/restapi/v2.1/accounts/{accountId}/...`. Formable: ```bash theme={null} curl https://api.formabledocs.com/v1/signature-requests \ --header "Authorization: Bearer fmbl_YOUR_API_KEY" ``` No `accountId` in the path. Create the key in [Settings](https://app.formabledocs.com/settings) and keep it on the server. Full details: [Authentication](/authentication). ## Migrate the flow ### 1. Create the signature request (was: create envelope) **Docusign** (simplified): ```json theme={null} { "templateId": "DOCUSIGN_TEMPLATE_ID", "templateRoles": [ { "email": "jane@example.com", "name": "Jane Doe", "roleName": "Signer", "clientUserId": "1000", "tabs": { "textTabs": [ { "tabLabel": "company", "value": "Acme Corporation" } ] } } ], "status": "sent" } ``` **Formable:** ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/signature-requests/embedded \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "role": "Signer" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "fields": [ { "fieldId": "field_company_name", "value": "Acme Corporation" } ], "testMode": true }' ``` ```json Response theme={null} { "signatureRequestId": "sr_456def", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "recipientSignatureId": "rsig_789ghi" } ] } ``` Pass a `signers` array on every create. Each signer needs `email`, `name`, and `role`. `sender` is optional and defaults to the org owner. Map tab / merge values to `fields` using Formable `fieldId`s from the template editor (not Docusign `tabLabel`s). Save each signer's `recipientSignatureId` to mint their signing URL. | Docusign | Formable | | -------------------------------- | --------------------------------------------------- | | `templateId` | `templateId` | | `templateRoles[].email` / `name` | `signers[].email` / `name` | | `roleName` | `signers[].role` | | `clientUserId` | Omit | | Tab values on the role | `fields: [{ fieldId, value }]` | | `status: "sent"` | Create, then mint the URL when ready | | Demo account testing | `testMode: true` (watermarked, not legally binding) | ### 2. Create the signing URL (was: recipient view) **Docusign:** ```bash theme={null} POST /v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/recipient ``` ```json theme={null} { "returnUrl": "https://yourapp.com/signing-complete", "authenticationMethod": "none", "email": "jane@example.com", "userName": "Jane Doe", "clientUserId": "1000" } ``` **Formable:** ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/recipient-signatures/rsig_789ghi/url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signingUrl": "https://app.formabledocs.com/sign/embedded/xyz789abc", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` No `clientUserId`, `returnUrl`, or `authenticationMethod`. You do not re-send the signer's email and name on the URL call — pass their `recipientSignatureId` from the create response. Formable URLs last **1 hour**. Creating a URL for an already completed request returns `409`. ### 3. Embed on the client ```html theme={null} ``` ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; if (event.data.type === "onSigningComplete") { // signer finished — close the iframe or show success } }); ``` Use `onSigningComplete` instead of a Docusign `returnUrl` redirect for client UX. Confirm completion with [`document_completed`](/webhooks/events#document_completed) or by polling `status` until `Completed` before downloading. See [Embedded signing](/walkthroughs/embedded-signing#detect-completion). ### 4. Download the signed PDF ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` Webhook verification uses `Content-Sha256` instead of `X-DocuSign-Signature-1`. See [Verifying webhooks](/webhooks/verifying-webhooks) and [Webhooks overview](/webhooks/overview). ## Differences to plan for Drop JWT / Auth Code token exchange for a Bearer API key on your server. The organization is implied by the key, so paths omit `accountId`. Embedded is the default. Create the request, then mint the URL when the user is ready. You do not mark recipients as captive at create time. Listen for `onSigningComplete` to update the client immediately. Confirm with a webhook or poll before downloading, then build any "signing finished" screen in your own app. Plan refresh logic around a **1 hour** TTL instead of Docusign's shorter recipient-view window. Still mint just in time; don't store URLs for later sessions. ## Checklist In [Settings](https://app.formabledocs.com/settings). Or migrate the [embedded template flow](/migrations/docusign/embedded-templates) separately. Use create embedded signature request with `signers` (including `role`), optional `sender`, and optional `fields`. Call create signing URL with each signer's `recipientSignatureId`; drop `clientUserId`, `returnUrl`, and `authenticationMethod`. Handle `document_completed` and download the signed envelope. ## Next steps Map Docusign template edit views to Formable. Full Formable signing walkthrough. Register endpoints and understand delivery. Bearer `fmbl_` API keys. # Docusign → Embedded templates Source: https://docs.formabledocs.com/migrations/docusign/embedded-templates Migrate Docusign embedded template editing to Formable's upload and editUrl flow. This guide helps you move Docusign template creation and embedded template editing to Formable. You still upload a document, open an editor for field placement, and reuse a `templateId` when sending. Formable collapses that into a multipart upload plus an `editUrl` iframe, without OAuth account paths or Anchor/AutoPlace tabs in the create payload. When templates are ready, continue with [Docusign → Embedded signing](/migrations/docusign/embedded-signing) to send envelopes for signature. New to Formable? Complete the [Embedded templates walkthrough](/walkthroughs/embedded-templates) first, then use this page as a translation layer from Docusign templates and template edit views. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Docusign integration to the Formable API. ## What you're moving In Docusign apps you typically: 1. Create or update a template (API and/or web UI) under an account 2. Define tabs with coordinates, anchors, or the sender / template UI 3. Optionally open an embedded template edit view (`TemplateViews:createEdit` and similar) so users adjust fields in your product 4. Send envelopes from that `templateId`, often with `templateRoles` and tab values In Formable you: 1. `POST /v1/templates` with the file 2. Embed `editUrl` so users place fields in your product 3. Copy each field's `id` for later prefills 4. Create signature requests with that `templateId` and a `signers` array You are migrating document preparation and in-product editing. Envelope send and recipient views live on the paired [signing guide](/migrations/docusign/embedded-signing). ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). Call [`POST /v1/templates`](/api-reference/endpoint/create-template) with PDF or Word files (max 40MB). Embed `editUrl` from create, or [`POST .../edit-url`](/api-reference/endpoint/create-template-edit-url) when it expires. Place fields in the Formable editor and update prefill code to use `fieldId` instead of Docusign `tabLabel` / anchors. Continue with [embedded signing migration](/migrations/docusign/embedded-signing). ## What's similar The high-level template model still matches Docusign: * Upload once, get a reusable template ID, send many times. * Users can edit field placement inside your product via a short-lived editor URL. * Prefills happen at send time against named field identifiers. * Template editing and signing use the same auth story on Formable (one Bearer key for both). If your Docusign product already embeds a template editor and stores `templateId` for later envelope creates, keep that product flow. Rebuild how the file is uploaded and how the editor URL is minted. ## What's different Plan for these Docusign-specific patterns that do not map 1:1: | Area | Docusign | Formable | | --------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | Auth | OAuth + account-scoped template APIs | Bearer `fmbl_…`; organization from the key | | Roles on create | Template roles defined up front | Optional `signer_roles` on create; assign roles to fields in the editor; pass `signers[].role` at send time | | Tab placement | Coordinates, Anchor / AutoPlace strings, or UI | Visual placement in Formable's editor | | Prefill keys | `tabLabel` / tab groups on roles | `fieldId` from the Formable editor | | Edit view | Template edit view API with account path | `editTemplateAccess.editUrl` or `POST .../edit-url` | | Editor TTL | Varies by view | Default about **1 day**; mint just in time | Anchor strings and absolute coordinates in create-envelope payloads do not carry over. Recreate positions in the Formable editor, then store the new `fieldId`s in your database or config. ## Concept map | Docusign | Formable | Notes | | ------------------------------------ | ----------------------------------------------------------------- | --------------------------------------------------- | | Template | Template | Upload once, reuse `templateId`. | | Tabs (SignHere, Text, DateSigned, …) | Fields (Signature, Text, Date, …) | Placed in Formable's visual editor. | | Template edit view URL | `editUrl` | Embed in an iframe. Expires (default **1 day**). | | Anchor / AutoPlace tabs | Visual placement in the editor | Recreate positions in the UI; prefill by `fieldId`. | | Template roles | Optional `signer_roles` on create + field placement in the editor | Pass matching `signers[].role` at send time. | ## Endpoint map | Docusign | Formable | | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | Create template / upload documents | [`POST /v1/templates`](/api-reference/endpoint/create-template) | | Template edit view (e.g. `TemplateViews:createEdit`) | [`POST /v1/templates/{templateId}/edit-url`](/api-reference/endpoint/create-template-edit-url) | | `editUrl` on create | `editTemplateAccess.editUrl` returned from create | ## Auth Docusign template APIs sit under `/v2.1/accounts/{accountId}/...` with an OAuth token. Formable uses the same Bearer key as signing: ```bash theme={null} curl https://api.formabledocs.com/v1/templates \ --header "Authorization: Bearer fmbl_YOUR_API_KEY" \ --form 'file=@./agreement.pdf' \ --form 'filename=agreement.pdf' ``` See [Authentication](/authentication). Call these endpoints from your backend only. ## Migrate the flow ### 1. Upload the template ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates \ --header "Authorization: Bearer $TOKEN" \ --form 'file=@./agreement.pdf' \ --form 'filename=agreement.pdf' \ --form 'signer_roles=[{"name":"Signer","order":0}]' ``` ```json Response theme={null} { "templateId": "abc123xyz", "editTemplateAccess": { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } } ``` There is no separate OAuth account path. The organization comes from the API key. Supported uploads: PDF, DOCX, and DOC, up to **40MB**. `signer_roles` is optional — pass it as a JSON string in multipart form data when you want named roles before opening the editor. You still assign those roles to required fields in the editor. ### 2. Get a fresh editor URL When the create URL has expired, or whenever a user needs to edit again: ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates/abc123xyz/edit-url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` This replaces Docusign's template edit view call. Generate just-in-time on your backend. Unknown IDs return `404`; templates outside your organization return `403`. ### 3. Embed on the client ```html theme={null} ``` Pass only the `editUrl` to the browser — never the API key. Template editing uses the same iframe pattern as [embedded signing](/migrations/docusign/embedded-signing). Listen for `onTemplateEditorSaved` when the user finishes editing — see [Embedded templates](/walkthroughs/embedded-templates#listen-for-editor-events). ## Tab → field mapping | Docusign tab | Formable field | | ------------------------- | -------------- | | SignHere / Signature | Signature | | Text / FullName / Company | Text | | DateSigned / Date | Date | | Checkbox | Checkbox | | Numerical / amount-style | Amount | If you previously set tab values on `templateRoles` when creating an envelope, use Formable `fields: [{ fieldId, value }]` at [signature request](/migrations/docusign/embedded-signing) time instead. Field IDs come from the editor after placement — see [prefills](/walkthroughs/embedded-signing#prefill-fields). ## Differences to plan for Anchor strings and absolute coordinates in the create-envelope payload don't carry over. Place fields in Formable's editor and reference `fieldId`s for prefills. Docusign templates often define roles up front. Formable supports optional `signer_roles` on template create, but you still assign roles to required fields in the editor and pass matching `signers[].role` when creating the signature request. Template create and edit-url calls use the same Bearer API key as signing — no account-scoped OAuth token. Docusign `tabLabel` values won't match Formable `fieldId`s. After users place fields, store the new IDs and update your send payload. ## Checklist In [Settings](https://app.formabledocs.com/settings). Call [`POST /v1/templates`](/api-reference/endpoint/create-template). Embed `editUrl` from create or [`POST .../edit-url`](/api-reference/endpoint/create-template-edit-url). Place fields in the editor and update prefill code. Continue with [embedded signing migration](/migrations/docusign/embedded-signing). ## Next steps Map envelopes and recipient views to Formable. Full Formable template editor walkthrough. How templates and signature requests fit together. Bearer `fmbl_` API keys. # Dropbox Sign → Embedded signing Source: https://docs.formabledocs.com/migrations/dropbox-sign/embedded-signing Migrate Dropbox Sign embedded signing (create_embedded, sign_url) to the Formable API. This guide helps you move a [Dropbox Sign embedded signing](https://developers.hellosign.com/docs/walkthroughs/embedded-signing) integration to Formable. You keep the same product shape (create a request, mint a URL, embed it) while swapping endpoints, auth, and how you open the signing UI. If you also embed template setup in your product, migrate that first with [Dropbox Sign → Embedded templates](/migrations/dropbox-sign/embedded-templates). Signing always starts from a prepared Formable template. New to Formable? Complete the [Embedded signing walkthrough](/walkthroughs/embedded-signing) first, then use this page as a translation layer from Dropbox Sign terms and calls. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Dropbox Sign integration to the Formable API. ## What you're moving In Dropbox Sign you typically: 1. Create an embedded signature request (`create_embedded` or `create_embedded_with_template`) 2. Fetch a per-signer `sign_url` with each `signature_id` 3. Open it with `hellosign-embedded` 4. React to callbacks such as `signature_request_all_signed`, then download files In Formable you: 1. Upload and prepare a **template** (once, then reuse) 2. Create an **embedded signature request** from that `templateId` with a `signers` array (each signer has `email`, `name`, and `role`) 3. Mint a `signingUrl` from each signer's `recipientSignatureId` and load it in a plain iframe 4. Handle `document_completed` (webhook or poll), then download the signed PDF The journey is familiar: backend creates the send, backend mints a short-lived URL, frontend embeds it. The main mental shifts are template-first sending, Bearer API keys, and no Dropbox Sign client library. ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). Recreate documents and fields in Formable. If you use embedded templates today, follow [Embedded templates migration](/migrations/dropbox-sign/embedded-templates). Replace `create_embedded*` and `/embedded/sign_url/{signature_id}` with [`POST /v1/signature-requests/embedded`](/api-reference/endpoint/create-signature-request) and [`POST /v1/recipient-signatures/{id}/url`](/api-reference/endpoint/create-signing-url). Pass only the `signingUrl` from your backend to the browser. Register a URL in Settings. See [Webhooks](/webhooks/overview). ## What's similar Much of the Dropbox Sign model still maps cleanly: * You still create a **signature request** on the server, then open a short-lived URL in the browser. * Templates remain the reusable document definition. One template powers many sends. * `test_mode` / `testMode` keeps integration traffic out of billing. Test mode documents are watermarked and are not legally binding. * You still download a completed PDF after everyone has signed. * Signing URLs are meant to be generated **just in time**, not stored long-term. Dropbox Sign `sign_url` values last about 60 minutes and same with Formable `signingUrl`. Migrating from Dropbox Sign to Formable is almost a 1 to 1 mapping. ## What's different Plan for these shifts early so you don't recreate Dropbox Sign patterns that Formable doesn't use: | Area | Dropbox Sign | Formable | | -------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Auth | API key | API key | | App / client | API App + `client_id` on requests and in the JS client | Organization implied by the API key; omit `client_id` | | Files on send | `create_embedded` can attach files inline | Upload a [template](/walkthroughs/embedded-templates) first, then create the signature request | | Signing URL scope | One `sign_url` per `signature_id` | One `signingUrl` per `recipientSignatureId` | | Client embed | `hellosign-embedded` + `client.open(...)` | Plain ` ``` ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; if (event.data.type === "onSigningComplete") { // signer finished — close the iframe or show success } if (event.data.type === "onSigningError") { // signing failed — show an error or retry } }); ``` Mint the URL on your backend and pass only the URL to the browser. Never call Formable with your API key from client-side code. See [Embedded signing](/walkthroughs/embedded-signing#detect-completion) for the full event list. ### 4. Detect completion and download the PDF Use `onSigningComplete` to update the client immediately. Prefer the [`document_completed`](/webhooks/events#document_completed) webhook (or poll signature request `status` until `Completed`) before downloading. You can also read the [event stream](/api-reference/endpoint/get-signature-request-events). ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` Download that presigned URL promptly. Requesting it before signing finishes returns `409`. Webhook verification differs from Dropbox Sign's `event_hash`. Formable signs the raw body with HMAC-SHA256 and sends the digest in `Content-Sha256`. See [Verifying webhooks](/webhooks/verifying-webhooks) and [Webhooks overview](/webhooks/overview). ## Differences to plan for Dropbox Sign can attach files on `create_embedded`. Formable expects a prepared [template](/walkthroughs/embedded-templates) first. Upload once, place fields, then send with `templateId`. Load `signingUrl` in an iframe. Listen for `onSigningComplete` / `onSigningError` instead of Dropbox Sign client events such as `sign` or `finish`. Confirm completion with webhooks or status polling before downloading the PDF. Dropbox Sign issues a `sign_url` per `signature_id`. Formable does the same: use each signer's `recipientSignatureId` from the create response with create signing URL. Dropbox Sign `signers[].role` maps to Formable `signers[].role`. Assign matching roles to required fields in the template editor, then pass the same role names when you create the embedded signature request. ## Checklist In [Settings](https://app.formabledocs.com/settings). Store it as a server secret. Recreate documents and fields — or migrate the [embedded template flow](/migrations/dropbox-sign/embedded-templates) separately. Replace `create_embedded*` and `embedded/sign_url` with Formable endpoints. Use `signers` with roles on create; mint URLs from each `recipientSignatureId`. Pass only the `signingUrl` from your backend. Handle `document_completed` / `document_viewed` and verify `Content-Sha256`. ## Next steps Map `create_embedded_draft` and `edit_url` to Formable. Map `send` / `send_with_template` to Formable email delivery. Full Formable signing walkthrough. Register endpoints and understand delivery. # Dropbox Sign → Embedded templates Source: https://docs.formabledocs.com/migrations/dropbox-sign/embedded-templates Migrate Dropbox Sign embedded templates (create_embedded_draft, edit_url) to the Formable API. This guide helps you move a [Dropbox Sign embedded templates](https://developers.hellosign.com/docs/walkthroughs/embedded-templates) integration to Formable. You still upload a document, open an editor URL in your product, and reuse a template ID when sending. The packaging changes: multipart create, Bearer auth, and a plain iframe instead of `hellosign-embedded`. When templates are ready, continue with [Dropbox Sign → Embedded signing](/migrations/dropbox-sign/embedded-signing) to send documents for signature. New to Formable? Complete the [Embedded templates walkthrough](/walkthroughs/embedded-templates) first, then use this page as a translation layer from Dropbox Sign draft and edit-url calls. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Dropbox Sign integration to the Formable API. ## What you're moving In Dropbox Sign you typically: 1. `POST /template/create_embedded_draft` with files, signer roles, and `client_id` 2. Open the returned `edit_url` (or fetch a fresh one via `/embedded/edit_url/{template_id}`) 3. Let the user place fields in the Dropbox Sign UI until the template is finalized 4. Listen for client events such as `createTemplate`, then use `template_id` on signature requests In Formable you: 1. `POST /v1/templates` with the file (`file` + `filename`) 2. Embed `editUrl` from the create response (or regenerate later) 3. Let the user place Signature / Text / Date / Checkbox / Amount fields in the Formable editor 4. Reuse `templateId` on every [signature request](/migrations/dropbox-sign/embedded-signing) As you can see, very similar flows. You are migrating the "prepare the document inside my product" path. Sending and signing stay on the paired signing guide. ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). Call [`POST /v1/templates`](/api-reference/endpoint/create-template) with the PDF or Word file. Use `editTemplateAccess.editUrl` from create, or [`POST .../edit-url`](/api-reference/endpoint/create-template-edit-url) when it expires. Pass only the `editUrl` from your backend. Listen for `onTemplateEditorSaved` instead of `createTemplate`. Continue with [embedded signing migration](/migrations/dropbox-sign/embedded-signing). ## What's similar The template lifecycle feels close to Dropbox Sign: * Upload a document once, get a stable template ID, reuse it on many sends. * Open a short-lived **editor URL** so users place fields without leaving your product. * Regenerate the editor URL when the previous one expires (Formable defaults to about **one day**). * The visual editor still owns field placement. Your API stores IDs and later prefills values at send time. If you already treat "create draft → open edit URL → wait for user → send with template ID" as the spine of your product, keep that spine. Only the endpoints and client packaging change. ## Comparisons | Area | Dropbox Sign | Formable | | -------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | Auth | API key | API key | | Create payload | Files, `signer_roles`, title, `client_id` | Multipart `file` + `filename`; optional `signer_roles` JSON string | | Draft vs ready | Embedded draft is finalized after the editor flow | `templateId` is returned immediately; users still must place fields before signing works end to end | | Client embed | `hellosign-embedded` + `createTemplate` event | Plain iframe + `window.postMessage` (`onTemplateEditorSaved`) | | Roles | Declared as `signer_roles` on create | Optional `signer_roles` on create; assign roles to fields in the editor; pass matching `signers[].role` at send time | Replace Dropbox Sign's `createTemplate` client event with Formable's `onTemplateEditorSaved` iframe `postMessage`. There is no Formable JS SDK — listen on `window` instead. ## Concept map | Dropbox Sign | Formable | Notes | | -------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- | | Embedded template draft | Template | Created by uploading a file. | | `template_id` | `templateId` | Reused on every signature request. | | `edit_url` | `editUrl` | Embed in an iframe. Expires (default **1 day**). | | `client_id` on draft / open | Organization | Implied by the API key. | | `hellosign-embedded` + `open(editUrl)` | Plain ` ``` ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; if (event.data.type === "onTemplateEditorSaved") { // template is ready — close the iframe or move to the next step } if (event.data.type === "onTemplateEditorError") { // save notification failed — show an error or retry } }); ``` Mint the URL on your backend and pass only the URL to the browser. Map Dropbox Sign's `createTemplate` to Formable's `onTemplateEditorSaved`. See [Embedded templates](/walkthroughs/embedded-templates#listen-for-editor-events) for the full event list. ## Field types | Dropbox Sign (typical) | Formable editor | | ---------------------- | --------------- | | Signature | Signature | | Text / merge fields | Text | | Date | Date | | Checkbox | Checkbox | | — | Amount | After placement, copy each field's `id` for [prefills](/walkthroughs/embedded-signing#prefill-fields) on signature requests. ## Differences to plan for Dropbox Sign embedded drafts go through an edit stage before the template is finalized. Formable returns a `templateId` on upload; users still need to place fields in the editor before signing works end to end. Use an iframe. Listen for `onTemplateEditorSaved` instead of the Dropbox Sign `createTemplate` client event. You can pass `signer_roles` as a JSON string when uploading, similar to Dropbox Sign. You still assign those roles to required fields in the editor, then pass matching `signers[].role` values when you [create the embedded signature request](/migrations/dropbox-sign/embedded-signing). Template `editUrl` and signing `signingUrl` both come from your backend and load in an iframe. You do not need two different Dropbox Sign-style client libraries. ## Checklist In [Settings](https://app.formabledocs.com/settings). Call [`POST /v1/templates`](/api-reference/endpoint/create-template) with the file. Use the create response `editUrl`, or [`POST .../edit-url`](/api-reference/endpoint/create-template-edit-url). Pass only the `editUrl` from your backend. Listen for `onTemplateEditorSaved`. Continue with [embedded signing migration](/migrations/dropbox-sign/embedded-signing). ## Next steps Map `sign_url` and signature requests to Formable. Full Formable template editor walkthrough. How templates and signature requests fit together. Bearer `fmbl_` API keys. # Dropbox Sign → Non-embedded signing Source: https://docs.formabledocs.com/migrations/dropbox-sign/non-embedded-signing Migrate Dropbox Sign non-embedded signing (signature_request/send, send_with_template) to the Formable API. This guide helps you move a [Dropbox Sign non-embedded signing](https://developers.hellosign.com/docs/walkthroughs/non-embedded-signing) integration to Formable. You keep the same product shape (create a request, Formable emails each signer a link, track status, download the PDF) while swapping endpoints and auth. If you prepare templates inside your product, migrate that first with [Dropbox Sign → Embedded templates](/migrations/dropbox-sign/embedded-templates). Signing always starts from a prepared Formable template. New to Formable? Complete the [Non-embedded signing walkthrough](/walkthroughs/non-embedded-signing) first, then use this page as a translation layer from Dropbox Sign terms and calls. Prefer a white-glove migration? Email [matt@formabledocs.com](mailto:matt@formabledocs.com) and we'll handle moving your Dropbox Sign integration to the Formable API. ## What you're moving In Dropbox Sign you typically: 1. Create a signature request with `send` (files inline) or `send_with_template` 2. Dropbox Sign emails each signer a link to sign on Dropbox Sign 3. Track status with callbacks (`signature_request_all_signed`, etc.) or by polling 4. Download completed files In Formable you: 1. Upload and prepare a **template** (once, then reuse) 2. Create a **regular signature request** from that `templateId` 3. Formable emails each signer a link to sign on Formable 4. Handle `document_completed` (webhook or poll), then download the signed PDF The journey is familiar: backend creates the send, the provider emails the signers, you react when everyone finishes. The main mental shifts are template-first sending and Bearer API keys. There is no client embed library for this flow. ## Recommended order of work Create an organization and key in [Settings](https://app.formabledocs.com/settings). See [Authentication](/authentication). Recreate documents and fields in Formable. If you use embedded templates today, follow [Embedded templates migration](/migrations/dropbox-sign/embedded-templates). Replace `signature_request/send` and `send_with_template` with Formable regular signature-request endpoints. Register a URL in Settings. See [Webhooks](/webhooks/overview). ## What's similar Much of the Dropbox Sign model still maps cleanly: * You still create a **signature request** on the server. The provider emails each signer. * Templates remain the reusable document definition. One template powers many sends. * `test_mode` / `testMode` keeps integration traffic out of billing. Test mode documents are watermarked and are not legally binding. * You still download a completed PDF after everyone has signed. * Webhooks remain the preferred way to learn when signing finishes; polling works but is less practical. Migrating non-embedded signing from Dropbox Sign to Formable is almost a 1 to 1 mapping for the email-based path. ## What's different Plan for these shifts early so you don't recreate Dropbox Sign patterns that Formable doesn't use: | Area | Dropbox Sign | Formable | | -------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Auth | API key (HTTP Basic) | API key (`Authorization: Bearer fmbl_…`) | | App / client | Optional API App + `client_id` on some calls | Organization implied by the API key; omit `client_id` | | Files on send | `send` can attach files inline | Upload a [template](/walkthroughs/embedded-templates) first, then create the regular signature request | | Delivery | Dropbox Sign emails each signer | Formable emails each signer — no signing URL to mint | | Status | Callbacks + `GET /signature_request/{id}` | [`document_completed`](/webhooks/events#document_completed) webhook or poll `status` | | Download | `GET /signature_request/files/{id}` | [`GET /v1/signature-requests/{id}/signed-envelope`](/api-reference/endpoint/get-signed-envelope) | | Webhook verification | `event_hash` | `Content-Sha256` HMAC — see [Verifying webhooks](/webhooks/verifying-webhooks) | Formable is intentionally template-first. If your Dropbox Sign code uploads PDFs on every `send`, move that upload into a one-time (or occasional) template create/edit step, then send with `templateId`. ## Concept map | Dropbox Sign | Formable | Notes | | ----------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- | | API key (HTTP Basic) | API key (`Authorization: Bearer fmbl_…`) | See [Authentication](/authentication). | | API App + `client_id` | Organization | Implied by the API key — not sent on requests. | | Signature request | Signature request | One send of a template to one or more signers. | | `send` / `send_with_template` | Regular signature request | Formable emails each signer. | | `signature_request_id` | `signatureRequestId` | Use for status, events, and download. | | `test_mode: true` | `testMode: true` | Excluded from billing. Documents are watermarked and not legally binding. | | `signature_request_all_signed` / `downloadable` | `document_completed` | Prefer the webhook before download. | ## Endpoint map | Dropbox Sign | Formable | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `POST /v3/signature_request/send_with_template` | [`POST /v1/signature-requests`](/api-reference/endpoint/create-regular-signature-request) | | `POST /v3/signature_request/send` (files inline) | Upload a [template](/walkthroughs/embedded-templates) first, then create the regular signature request | | `GET /v3/signature_request/{signature_request_id}` | [`GET /v1/signature-requests/{id}`](/api-reference/endpoint/get-signature-request) | | `GET /v3/signature_request/files/{signature_request_id}` | [`GET /v1/signature-requests/{id}/signed-envelope`](/api-reference/endpoint/get-signed-envelope) | ## Auth Dropbox Sign typically uses HTTP Basic: ```bash theme={null} curl https://api.hellosign.com/v3/account -u 'YOUR_API_KEY:' ``` Formable uses Bearer tokens that start with `fmbl_`: ```bash theme={null} curl https://api.formabledocs.com/v1/signature-requests \ --header "Authorization: Bearer fmbl_YOUR_API_KEY" ``` Create the key in [Settings](https://app.formabledocs.com/settings) and call the API only from your backend. Full details: [Authentication](/authentication). ## Migrate the flow ### 1. Create the signature request **Dropbox Sign** (`send_with_template`): ```bash theme={null} curl https://api.hellosign.com/v3/signature_request/send_with_template \ -u 'YOUR_API_KEY:' \ -F 'template_ids[]=TEMPLATE_ID' \ -F 'subject=Purchase Order' \ -F 'message=Glad we could come to an agreement.' \ -F 'signers[0][role]=Client' \ -F 'signers[0][name]=Jane Doe' \ -F 'signers[0][email_address]=jane@example.com' \ -F 'test_mode=1' ``` **Dropbox Sign** (`send` with files): ```bash theme={null} curl https://api.hellosign.com/v3/signature_request/send \ -u 'YOUR_API_KEY:' \ -F 'files[0]=@mutual-NDA-example.pdf' \ -F 'title=NDA with Acme Co.' \ -F 'signers[0][email_address]=jane@example.com' \ -F 'signers[0][name]=Jane Doe' \ -F 'signers[0][order]=0' \ -F 'test_mode=1' ``` For file-based sends, create a Formable [template](/walkthroughs/embedded-templates) once (place fields in the editor), then reuse `templateId` on every request. **Formable:** ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/signature-requests \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "role": "Client" }, { "email": "bob@example.com", "name": "Bob Smith", "role": "Witness" } ], "testMode": true }' ``` ```json Response theme={null} { "signatureRequestId": "sr_456def", "templateId": "abc123xyz", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "recipientSignatureId": "rsig_jane123" }, { "email": "bob@example.com", "name": "Bob Smith", "recipientSignatureId": "rsig_bob456" } ], "sender": { "email": "owner@example.com", "name": "Org Owner" }, "status": "Created", "testMode": true } ``` Formable emails each signer automatically. You do **not** generate a signing URL for this flow. | Dropbox Sign field | Formable field | | ---------------------------------- | ------------------------------------------------------------ | | `template_ids[]` | `templateId` (one template per request) | | `files[]` / form fields on send | Prepare a [template](/walkthroughs/embedded-templates) first | | `signers[].email_address` / `name` | `signers[].email` / `name` | | `signers[].role` | `signers[].role` (matches template field roles) | | `signers[].order` | Signing order follows the `signers` array order | | `subject` / `message` / `title` | Omit | | `cc_email_addresses` / `ccs` | Omit | | `client_id` | Omit | | `test_mode` | `testMode` | ### 2. Detect completion Prefer the [`document_completed`](/webhooks/events#document_completed) webhook. You can also poll signature request `status` until `Completed`, or read the [event stream](/api-reference/endpoint/get-signature-request-events). **Dropbox Sign** (poll): ```bash theme={null} curl https://api.hellosign.com/v3/signature_request/SIGNATURE_REQUEST_ID \ -u 'YOUR_API_KEY:' ``` **Formable** (poll): ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def \ --header "Authorization: Bearer $TOKEN" ``` Webhook verification differs from Dropbox Sign's `event_hash`. Formable signs the raw body with HMAC-SHA256 and sends the digest in `Content-Sha256`. See [Verifying webhooks](/webhooks/verifying-webhooks) and [Webhooks overview](/webhooks/overview). ### 3. Download the PDF **Dropbox Sign:** ```bash theme={null} curl https://api.hellosign.com/v3/signature_request/files/SIGNATURE_REQUEST_ID?file_type=pdf \ -u 'YOUR_API_KEY:' \ --output downloaded_document.pdf ``` **Formable:** ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` Download that presigned URL promptly. Requesting it before signing finishes returns `409`. ## Differences to plan for Dropbox Sign can attach files on `send`, or place fields with `form_fields_per_document`. Formable expects a prepared [template](/walkthroughs/embedded-templates) first. Upload once, place fields, then send with `templateId`. Non-embedded Dropbox Sign and Formable both email the signer. Do not call Formable's [create signing URL](/api-reference/endpoint/create-signing-url) endpoint for this flow — that path is for [embedded signing](/migrations/dropbox-sign/embedded-signing). Dropbox Sign `signers[].role` maps to Formable `signers[].role`. Assign matching roles to required fields in the template editor, then pass the same role names when you create the regular signature request. Dropbox Sign `subject`, `message`, `title`, and CC addresses are not parameters on Formable regular signature requests. Plan email copy and CC behavior in your own product if you relied on those fields. ## Checklist In [Settings](https://app.formabledocs.com/settings). Store it as a server secret. Recreate documents and fields — or migrate the [embedded template flow](/migrations/dropbox-sign/embedded-templates) separately. Replace `signature_request/send` and `send_with_template` with [`POST /v1/signature-requests`](/api-reference/endpoint/create-regular-signature-request). Handle `document_completed` / `document_viewed` and verify `Content-Sha256`. ## Next steps If you also embed signing in an iframe. Full Formable email-based signing walkthrough. Register endpoints and understand delivery. Bearer `fmbl_` API keys. # Embedded Signing Quickstart Source: https://docs.formabledocs.com/quickstart Create your first signature request and get an embeddable signing URL. This quickstart takes about **10 minutes**. In just **4 API calls**, you go from a plain PDF, DOC, or DOCX to a signed document with an audit trail. ## Prerequisites Before you begin, you need: * An **API key** from [Settings](https://app.formabledocs.com/settings) — see [Set up a developer account](/developer-account) and [Authentication](/authentication) * A **PDF, DOC, or DOCX file** under 40MB to send for signature The examples below use the production base URL `https://api.formabledocs.com/v1`. Replace `$TOKEN` with your API key (it starts with `fmbl_`). Prefer a typed client? The same four calls are in the [official SDKs](/sdks/overview). ## Steps Upload your document as `multipart/form-data`. Formable returns a `templateId` you'll use to send the document. Optionally pass `signer_roles` so you can assign roles to fields in the editor. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates \ --header "Authorization: Bearer $TOKEN" \ --form 'file=@./agreement.pdf' \ --form 'filename=agreement.pdf' \ --form 'signer_roles=[{"name":"Client","order":0}]' ``` ```json Response theme={null} { "templateId": "abc123xyz", "editTemplateAccess": { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } } ``` Open the `editUrl` in a browser to place signature, text, and date fields on the document. Assign each required field a signer role (for example `Client`). See [Embedded templates](/walkthroughs/embedded-templates). Template editor with a Signature field selected over the Customer line, showing Required toggle and Reference ID in the sidebar Create a signature request from the template. Provide `signers` (who signs, with a `role` that matches required field roles on the template). `sender` is optional and defaults to the API key organization owner. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/signature-requests/embedded \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "signers": [ { "email": "signer@example.com", "name": "Jane Doe", "role": "Client" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "testMode": true }' ``` ```json Response theme={null} { "signatureRequestId": "sr_456def", "templateId": "abc123xyz", "signers": [ { "email": "signer@example.com", "name": "Jane Doe", "recipientSignatureId": "rsig_789ghi" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "status": "Created", "testMode": true } ``` Save each signer's `recipientSignatureId`. You'll use it to mint that person's signing URL. Turn a recipient signature into an embeddable signing URL. The URL expires one hour after creation. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/recipient-signatures/rsig_789ghi/url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signingUrl": "https://app.formabledocs.com/sign/embedded/xyz789abc", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` Load the `signingUrl` in an iframe or redirect the signer to it (link does not need to be embedded in iframe to be opened): ```html theme={null} ``` Embedded signing view showing document.docx with a Sign here field on the Customer line, a Required callout, and a Start button Once the signer completes the document, download the signed PDF from a presigned URL. ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` This endpoint returns `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll [signature request events](/api-reference/endpoint/get-signature-request-events) / the [signature request](/api-reference/endpoint/get-signature-request) `status` until it's `Completed`. The signed PDF includes an audit trail with created, sent, signed, and completed events. Formable audit trail for document.docx showing Created, Sent, Signed, and Completed events with status Completed ## Next steps Prefill fields, embed signing, detect completion, and download the signed PDF. Email each signer a signing link. Place fields and reuse documents across signature requests. Get notified when a document is viewed or signed. Need contract negotiation before signing? See the optional [redlining walkthrough](/walkthroughs/redlining). # .NET Source: https://docs.formabledocs.com/sdks/dotnet Install and use Formable, the official .NET SDK for the Formable API. [`Formable`](https://www.nuget.org/packages/Formable) is the official .NET SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Typed request and response models * Async methods with `CancellationToken` support * Zero runtime dependencies on .NET 8+ Source code is on [GitHub](https://github.com/FormableDocs/formable-dotnet). ## Installation ```bash theme={null} dotnet add package Formable ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```csharp theme={null} using Formable; using var formable = new FormableClient(Environment.GetEnvironmentVariable("FORMABLE_API_KEY")); ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). ```csharp theme={null} var created = await formable.Templates.CreateAsync( "nda.docx", [ new TemplateSignerRole("Client", 0), new TemplateSignerRole("Witness", 1), ]); string templateId = created.TemplateId; // Mint a fresh edit URL later (expires after 1 day) var edit = await formable.Templates.CreateEditUrlAsync(templateId); ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```csharp theme={null} // Formable emails each signer a signing link var request = await formable.SignatureRequests.CreateAsync( new CreateSignatureRequest( templateId, [ new Signer("jane@example.com", "Jane Doe", "Client"), new Signer("bob@example.com", "Bob Smith", "Witness"), ])); // Embedded flow: mint signing URLs to embed in an iframe yourself var embedded = await formable.SignatureRequests.CreateEmbeddedAsync( new CreateSignatureRequest( templateId, [new Signer("jane@example.com", "Jane Doe", "Client")], TestMode: true)); var signer = embedded.Signers[0]; var signing = await formable.SignatureRequests.CreateSigningUrlAsync(signer.RecipientSignatureId); // Track progress var current = await formable.SignatureRequests.GetAsync(embedded.SignatureRequestId); var all = await formable.SignatureRequests.ListAsync(DateTimeOffset.Parse("2026-01-01T00:00:00Z")); var events = await formable.SignatureRequests.GetEventsAsync(embedded.SignatureRequestId); // Download the signed document once completed var envelope = await formable.SignatureRequests.GetSignedEnvelopeAsync(embedded.SignatureRequestId); ``` `GetSignedEnvelopeAsync` throws a `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `GetAsync` until `Status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```csharp theme={null} var created = await formable.RedlineRequests.CreateAsync( new CreateRedlineRequest( templateId, [ new RedlineMember("us@example.com", "John Doe", RedlineMemberRole.DisclosingParty), new RedlineMember("them@example.com", "Jane Smith", RedlineMemberRole.ReceivingParty), ], Metadata: new RedlineRequestMetadata("Mutual NDA"))); string redlineRequestId = created.RedlineRequestId; // Mint a redline URL for a member (embed in an iframe) var url = await formable.RedlineRequests.CreateUrlAsync(redlineRequestId, "them@example.com"); // Manage members and track progress await formable.RedlineRequests.UpdateMembersAsync( redlineRequestId, [new RedlineMember("counsel@example.com", "Counsel", RedlineMemberRole.ReceivingCounsel)]); var redline = await formable.RedlineRequests.GetAsync(redlineRequestId); var events = await formable.RedlineRequests.GetEventsAsync(redlineRequestId); ``` ## Billing and health ```csharp theme={null} var billing = await formable.BillingAsync(); var health = await formable.HealthAsync(); ``` ## Error handling All non-2xx responses throw a `FormableException` with the server's error message, HTTP status, and parsed response body. ```csharp theme={null} try { await formable.SignatureRequests.GetAsync("missing-id"); } catch (FormableException error) { Console.Error.WriteLine($"{error.Status} {error.Message}"); } ``` ## Configuration `FormableOptions` takes an API key, then optional `BaseUrl`, `Timeout`, and `HttpClient` values. ```csharp theme={null} using Formable; using var formable = new FormableClient( new FormableOptions { ApiKey = Environment.GetEnvironmentVariable("FORMABLE_API_KEY"), BaseUrl = "https://api.formabledocs.com/v1", Timeout = TimeSpan.FromSeconds(60), HttpClient = httpClient, }); ``` | Option | Description | Default | | ------------ | --------------------------------------------------------- | --------------------------------- | | `ApiKey` | Your Formable API key (sent as a bearer token). Required. | - | | `BaseUrl` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `Timeout` | Per-request timeout. | 60 seconds | | `HttpClient` | Custom `HttpClient`. Not disposed by the SDK. | Built-in client with 60s timeout | Pass an `HttpClient` from `IHttpClientFactory` in ASP.NET Core so the factory owns the handler lifetime. ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Go Source: https://docs.formabledocs.com/sdks/go Install and use formable-go, the official Go SDK for the Formable API. [`formable-go`](https://pkg.go.dev/github.com/FormableDocs/formable-go) is the official Go SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Typed request and response models * Uses the Go standard library (`net/http`) * Context on every method * Zero runtime dependencies (Go 1.22+) Source code is on [GitHub](https://github.com/FormableDocs/formable-go). ## Installation ```bash theme={null} go get github.com/FormableDocs/formable-go ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```go theme={null} import ( "context" "log" "os" "github.com/FormableDocs/formable-go" ) ctx := context.Background() client, err := formable.NewClient(os.Getenv("FORMABLE_API_KEY")) if err != nil { log.Fatal(err) } ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). ```go theme={null} created, err := client.Templates.CreateFromFile( ctx, "nda.docx", []formable.TemplateSignerRole{ {Name: "Client", Order: 0}, {Name: "Witness", Order: 1}, }, ) templateID := created.TemplateID // Mint a fresh edit URL later (expires after 1 day) edit, err := client.Templates.CreateEditURL(ctx, templateID) ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```go theme={null} // Formable emails each signer a signing link request, err := client.SignatureRequests.Create(ctx, &formable.CreateSignatureRequest{ TemplateID: templateID, Signers: []formable.Signer{ {Email: "jane@example.com", Name: "Jane Doe", Role: "Client"}, {Email: "bob@example.com", Name: "Bob Smith", Role: "Witness"}, }, }) // Embedded flow: mint signing URLs to embed in an iframe yourself embedded, err := client.SignatureRequests.CreateEmbedded(ctx, &formable.CreateSignatureRequest{ TemplateID: templateID, Signers: []formable.Signer{{Email: "jane@example.com", Name: "Jane Doe", Role: "Client"}}, TestMode: true, }) signer := embedded.Signers[0] signing, err := client.SignatureRequests.CreateSigningURL(ctx, signer.RecipientSignatureID) // Track progress current, err := client.SignatureRequests.Get(ctx, embedded.SignatureRequestID) all, err := client.SignatureRequests.List(ctx, &formable.ListOptions{ UpdatedSince: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), }) events, err := client.SignatureRequests.GetEvents(ctx, embedded.SignatureRequestID) // Download the signed document once completed envelope, err := client.SignatureRequests.GetSignedEnvelope(ctx, embedded.SignatureRequestID) ``` `GetSignedEnvelope` returns a `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `Get` until `Status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```go theme={null} created, err := client.RedlineRequests.Create(ctx, &formable.CreateRedlineRequest{ TemplateID: templateID, Members: []formable.RedlineMember{ {Email: "us@example.com", DisplayName: "John Doe", Role: formable.RedlineMemberRoleDisclosingParty}, {Email: "them@example.com", DisplayName: "Jane Smith", Role: formable.RedlineMemberRoleReceivingParty}, }, Metadata: &formable.RedlineRequestMetadata{Subject: "Mutual NDA"}, }) redlineRequestID := created.RedlineRequestID // Mint a redline URL for a member (embed in an iframe) url, err := client.RedlineRequests.CreateURL(ctx, redlineRequestID, "them@example.com") // Manage members and track progress _, err = client.RedlineRequests.UpdateMembers(ctx, redlineRequestID, []formable.RedlineMember{ {Email: "counsel@example.com", DisplayName: "Counsel", Role: formable.RedlineMemberRoleReceivingCounsel}, }) redline, err := client.RedlineRequests.Get(ctx, redlineRequestID) events, err := client.RedlineRequests.GetEvents(ctx, redlineRequestID) ``` ## Billing and health ```go theme={null} billing, err := client.Billing(ctx) health, err := client.Health(ctx) ``` ## Error handling All non-2xx responses return a `*formable.Error` with the server's error message, HTTP status, and parsed response body. ```go theme={null} _, err := client.SignatureRequests.Get(ctx, "missing-id") var apiErr *formable.Error if errors.As(err, &apiErr) { fmt.Fprintf(os.Stderr, "%d %s\n", apiErr.Status, apiErr.Error()) } ``` ## Configuration Functional options take an API key, then optional base URL, timeout, and `*http.Client` values. ```go theme={null} client, err := formable.NewClient( os.Getenv("FORMABLE_API_KEY"), formable.WithBaseURL("https://api.formabledocs.com/v1"), formable.WithTimeout(60*time.Second), formable.WithHTTPClient(httpClient), ) ``` | Option | Description | Default | | ---------------- | --------------------------------------------------------- | --------------------------------- | | (positional) | Your Formable API key (sent as a bearer token). Required. | - | | `WithBaseURL` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `WithTimeout` | Per-request timeout. | 60 seconds | | `WithHTTPClient` | Custom `*http.Client`. Not closed by the SDK. | Built-in client | ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Java Source: https://docs.formabledocs.com/sdks/java Install and use formable-sdk, the official Java SDK for the Formable API. [`formable-sdk`](https://central.sonatype.com/artifact/com.formabledocs/formable-sdk) is the official Java SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Typed request and response models * Uses the JDK `HttpClient` (Java 17+) * One runtime dependency: Jackson Databind Source code is on [GitHub](https://github.com/FormableDocs/formable-java). ## Installation ```xml Maven theme={null} com.formabledocs formable-sdk 0.1.0 ``` ```kotlin Gradle theme={null} implementation("com.formabledocs:formable-sdk:0.1.0") ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```java theme={null} import com.formabledocs.Formable; Formable formable = new Formable(System.getenv("FORMABLE_API_KEY")); ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). ```java theme={null} import com.formabledocs.model.TemplateSignerRole; import java.nio.file.Path; import java.util.List; var created = formable.templates.create( Path.of("nda.docx"), List.of( new TemplateSignerRole("Client", 0), new TemplateSignerRole("Witness", 1) ) ); String templateId = created.templateId(); // Mint a fresh edit URL later (expires after 1 day) var edit = formable.templates.createEditUrl(templateId); ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```java theme={null} import com.formabledocs.model.CreateSignatureRequest; import com.formabledocs.model.Signer; import java.time.Instant; // Formable emails each signer a signing link var request = formable.signatureRequests.create( CreateSignatureRequest.builder() .templateId(templateId) .addSigner(new Signer("jane@example.com", "Jane Doe", "Client")) .addSigner(new Signer("bob@example.com", "Bob Smith", "Witness")) .build() ); // Embedded flow: mint signing URLs to embed in an iframe yourself var embedded = formable.signatureRequests.createEmbedded( CreateSignatureRequest.builder() .templateId(templateId) .addSigner(new Signer("jane@example.com", "Jane Doe", "Client")) .testMode(true) .build() ); var signer = embedded.signers().get(0); var signing = formable.signatureRequests.createSigningUrl(signer.recipientSignatureId()); // Track progress var current = formable.signatureRequests.get(embedded.signatureRequestId()); var all = formable.signatureRequests.list(Instant.parse("2026-01-01T00:00:00Z")); var events = formable.signatureRequests.getEvents(embedded.signatureRequestId()); // Download the signed document once completed var envelope = formable.signatureRequests.getSignedEnvelope(embedded.signatureRequestId()); ``` `getSignedEnvelope` throws a `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `get` until `status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```java theme={null} import com.formabledocs.model.CreateRedlineRequest; import com.formabledocs.model.RedlineMember; import com.formabledocs.model.RedlineMemberRole; import com.formabledocs.model.RedlineRequestMetadata; import java.util.List; var created = formable.redlineRequests.create( CreateRedlineRequest.builder() .templateId(templateId) .addMember(new RedlineMember("us@example.com", "John Doe", RedlineMemberRole.DISCLOSING_PARTY)) .addMember(new RedlineMember("them@example.com", "Jane Smith", RedlineMemberRole.RECEIVING_PARTY)) .metadata(new RedlineRequestMetadata("Mutual NDA")) .build() ); String redlineRequestId = created.redlineRequestId(); // Mint a redline URL for a member (embed in an iframe) var url = formable.redlineRequests.createUrl(redlineRequestId, "them@example.com"); // Manage members and track progress formable.redlineRequests.updateMembers( redlineRequestId, List.of(new RedlineMember("counsel@example.com", "Counsel", RedlineMemberRole.RECEIVING_COUNSEL)) ); var redline = formable.redlineRequests.get(redlineRequestId); var events = formable.redlineRequests.getEvents(redlineRequestId); ``` ## Billing and health ```java theme={null} var billing = formable.billing(); var health = formable.health(); ``` ## Error handling All non-2xx responses throw a `FormableException` with the server's error message, HTTP status, and parsed response body. ```java theme={null} import com.formabledocs.FormableException; try { formable.signatureRequests.get("missing-id"); } catch (FormableException error) { System.err.println(error.status() + " " + error.getMessage()); } ``` ## Configuration The builder takes an API key, then optional `baseUrl`, `timeout`, and JDK `HttpClient` values. ```java theme={null} import com.formabledocs.Formable; import java.net.http.HttpClient; import java.time.Duration; Formable formable = Formable.builder() .apiKey(System.getenv("FORMABLE_API_KEY")) .baseUrl("https://api.formabledocs.com/v1") .timeout(Duration.ofSeconds(60)) .httpClient(HttpClient.newHttpClient()) .build(); ``` | Option | Description | Default | | ------------ | --------------------------------------------------------- | ----------------------------------- | | `apiKey` | Your Formable API key (sent as a bearer token). Required. | - | | `baseUrl` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `timeout` | Per-request timeout. | 60 seconds | | `httpClient` | Custom `java.net.http.HttpClient`. | JDK client with 60s connect timeout | ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Node Source: https://docs.formabledocs.com/sdks/node Install and use formable-node, the official Node.js SDK for the Formable API. [`formable-node`](https://www.npmjs.com/package/formable-node) is the official Node.js SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Zero runtime dependencies (uses native `fetch`) * Full TypeScript types for every request and response * Node.js 18+ Source code is on [GitHub](https://github.com/FormableDocs/formable-node). ## Installation ```bash npm theme={null} npm install formable-node ``` ```bash bun theme={null} bun add formable-node ``` ```bash yarn theme={null} yarn add formable-node ``` ```bash pnpm theme={null} pnpm add formable-node ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```ts theme={null} import Formable from "formable-node"; const formable = new Formable({ apiKey: process.env.FORMABLE_API_KEY! }); ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). ```ts theme={null} import { readFile } from "node:fs/promises"; const file = await readFile("./nda.docx"); const { templateId, editTemplateAccess } = await formable.templates.create({ file, filename: "nda.docx", signerRoles: [ { name: "Client", order: 0 }, { name: "Witness", order: 1 }, ], }); // Mint a fresh edit URL later (expires after 1 day) const { editUrl, expiresAt } = await formable.templates.createEditUrl(templateId); ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```ts theme={null} // Formable emails each signer a signing link const request = await formable.signatureRequests.create({ templateId, signers: [ { email: "jane@example.com", name: "Jane Doe", role: "Client" }, { email: "bob@example.com", name: "Bob Smith", role: "Witness" }, ], }); // Embedded flow: mint signing URLs to embed in an iframe yourself const embedded = await formable.signatureRequests.createEmbedded({ templateId, signers: [{ email: "jane@example.com", name: "Jane Doe", role: "Client" }], testMode: true, }); const [signer] = embedded.signers; const { signingUrl } = await formable.signatureRequests.createSigningUrl( signer.recipientSignatureId ); // Track progress const current = await formable.signatureRequests.get(embedded.signatureRequestId); const all = await formable.signatureRequests.list({ updatedSince: new Date("2026-01-01") }); const { signatureRequestEvents } = await formable.signatureRequests.getEvents( embedded.signatureRequestId ); // Download the signed document once completed const { signedEnvelopePresignedUrl } = await formable.signatureRequests.getSignedEnvelope(embedded.signatureRequestId); ``` `getSignedEnvelope` returns `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `get` until `status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```ts theme={null} const { redlineRequestId } = await formable.redlineRequests.create({ templateId, members: [ { email: "us@example.com", displayName: "John Doe", role: "DisclosingParty" }, { email: "them@example.com", displayName: "Jane Smith", role: "ReceivingParty" }, ], metadata: { subject: "Mutual NDA" }, }); // Mint a redline URL for a member (embed in an iframe) const { redlineUrl } = await formable.redlineRequests.createUrl( redlineRequestId, "them@example.com" ); // Manage members and track progress await formable.redlineRequests.updateMembers(redlineRequestId, [ { email: "counsel@example.com", displayName: "Counsel", role: "ReceivingCounsel" }, ]); const redline = await formable.redlineRequests.get(redlineRequestId); const { redlineRequestEvents } = await formable.redlineRequests.getEvents(redlineRequestId); ``` ## Billing and health ```ts theme={null} const { numberOfRedliningSessions } = await formable.billing(); const health = await formable.health(); ``` ## Error handling All non-2xx responses throw a `FormableError` with the server's error message, HTTP status, and parsed response body. ```ts theme={null} import { FormableError } from "formable-node"; try { await formable.signatureRequests.get("missing-id"); } catch (error) { if (error instanceof FormableError) { console.error(error.status, error.message); } } ``` ## Configuration | Option | Description | Default | | --------- | --------------------------------------------------------- | --------------------------------- | | `apiKey` | Your Formable API key (sent as a bearer token). Required. | - | | `baseUrl` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `fetch` | Custom `fetch` implementation. | `globalThis.fetch` | ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Overview Source: https://docs.formabledocs.com/sdks/overview Official Formable SDKs for building with the Formable API in your preferred language. Formable provides official SDKs (software development kits) to build with the Formable API using your preferred programming language. The SDKs are helper libraries that wrap the [REST API](/api-reference/introduction) with typed requests and responses, so you don't have to hand-roll HTTP calls. Formable SDKs are licensed under the MIT license. ## Official Formable SDKs | Language | Quick reference | Package | | -------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ | | Node.js | [Node SDK overview](/sdks/node) | [`formable-node`](https://www.npmjs.com/package/formable-node) | | Python | [Python SDK overview](/sdks/python) | [`formable-sdk`](https://pypi.org/project/formable-sdk/) | | PHP | [PHP SDK overview](/sdks/php) | [`formable/formable-sdk`](https://packagist.org/packages/formable/formable-sdk) | | Java | [Java SDK overview](/sdks/java) | [`com.formabledocs:formable-sdk`](https://central.sonatype.com/artifact/com.formabledocs/formable-sdk) | | Go | [Go SDK overview](/sdks/go) | [`formable-go`](https://pkg.go.dev/github.com/FormableDocs/formable-go) | | Ruby | [Ruby SDK overview](/sdks/ruby) | [`formable`](https://rubygems.org/gems/formable) | | .NET | [.NET SDK overview](/sdks/dotnet) | [`Formable`](https://www.nuget.org/packages/Formable) | Every SDK has full feature parity with the API: templates, signature requests, redlining, billing, and health checks. ## Other languages If your language doesn't have an official SDK yet, call the [REST API](/api-reference/introduction) directly. Every endpoint page in the API reference includes ready-to-copy examples for cURL and more. Install `formable-node` and make your first API call in TypeScript or JavaScript. Install `formable-sdk` and make your first API call with the sync or async client. Install `formable/formable-sdk` and make your first API call in PHP. Install `com.formabledocs:formable-sdk` and make your first API call in Java 17+. Install `formable-go` and make your first API call in Go 1.22+. Install `formable` and make your first API call in Ruby 3.1+. Install `Formable` and make your first API call in .NET 8+. # PHP Source: https://docs.formabledocs.com/sdks/php Install and use formable/formable-sdk, the official PHP SDK for the Formable API. [`formable/formable-sdk`](https://packagist.org/packages/formable/formable-sdk) is the official PHP SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Guzzle HTTP client (injectable) * PHP 8.1+ Source code is on [GitHub](https://github.com/FormableDocs/formable-php). ## Installation ```bash theme={null} composer require formable/formable-sdk ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```php theme={null} use Formable\Formable; $formable = new Formable(getenv('FORMABLE_API_KEY')); ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). `file` accepts a path, raw contents, or a PSR-7 stream. ```php theme={null} $result = $formable->templates->create( file: './nda.docx', filename: 'nda.docx', signerRoles: [ ['name' => 'Client', 'order' => 0], ['name' => 'Witness', 'order' => 1], ], ); $templateId = $result['templateId']; // Mint a fresh edit URL later (expires after 1 day) $edit = $formable->templates->createEditUrl($templateId); ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```php theme={null} // Formable emails each signer a signing link $request = $formable->signatureRequests->create( templateId: $templateId, signers: [ ['email' => 'jane@example.com', 'name' => 'Jane Doe', 'role' => 'Client'], ['email' => 'bob@example.com', 'name' => 'Bob Smith', 'role' => 'Witness'], ], ); // Embedded flow: mint signing URLs to embed in an iframe yourself $embedded = $formable->signatureRequests->createEmbedded( templateId: $templateId, signers: [['email' => 'jane@example.com', 'name' => 'Jane Doe', 'role' => 'Client']], testMode: true, ); $signer = $embedded['signers'][0]; $signing = $formable->signatureRequests->createSigningUrl( $signer['recipientSignatureId'] ); // Track progress $current = $formable->signatureRequests->get($embedded['signatureRequestId']); $all = $formable->signatureRequests->list( updatedSince: new DateTimeImmutable('2026-01-01T00:00:00Z') ); $events = $formable->signatureRequests->getEvents($embedded['signatureRequestId']); // Download the signed document once completed $envelope = $formable->signatureRequests->getSignedEnvelope( $embedded['signatureRequestId'] ); ``` `getSignedEnvelope` throws a `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `get` until `status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```php theme={null} $created = $formable->redlineRequests->create( templateId: $templateId, members: [ ['email' => 'us@example.com', 'displayName' => 'John Doe', 'role' => 'DisclosingParty'], ['email' => 'them@example.com', 'displayName' => 'Jane Smith', 'role' => 'ReceivingParty'], ], metadata: ['subject' => 'Mutual NDA'], ); $redlineRequestId = $created['redlineRequestId']; // Mint a redline URL for a member (embed in an iframe) $url = $formable->redlineRequests->createUrl($redlineRequestId, 'them@example.com'); // Manage members and track progress $formable->redlineRequests->updateMembers($redlineRequestId, [ ['email' => 'counsel@example.com', 'displayName' => 'Counsel', 'role' => 'ReceivingCounsel'], ]); $redline = $formable->redlineRequests->get($redlineRequestId); $events = $formable->redlineRequests->getEvents($redlineRequestId); ``` ## Billing and health ```php theme={null} $billing = $formable->billing(); $health = $formable->health(); ``` ## Error handling All non-2xx responses throw a `FormableError` with the server's error message, HTTP status, and parsed response body. ```php theme={null} use Formable\FormableError; try { $formable->signatureRequests->get('missing-id'); } catch (FormableError $error) { error_log($error->status.' '.$error->getMessage()); } ``` ## Configuration The client constructor takes an API key, then optional `baseUrl` and Guzzle `client` arguments. ```php theme={null} use Formable\Formable; use GuzzleHttp\Client; $formable = new Formable( apiKey: getenv('FORMABLE_API_KEY'), baseUrl: 'https://api.formabledocs.com/v1', client: new Client(['timeout' => 60]), ); ``` | Option | Description | Default | | --------- | --------------------------------------------------------- | --------------------------------- | | `apiKey` | Your Formable API key (sent as a bearer token). Required. | - | | `baseUrl` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `client` | Custom `GuzzleHttp\ClientInterface`. | Built-in client with 60s timeout | ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Python Source: https://docs.formabledocs.com/sdks/python Install and use formable-sdk, the official Python SDK for the Formable API. [`formable-sdk`](https://pypi.org/project/formable-sdk/) is the official Python SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Sync (`Formable`) and async (`AsyncFormable`) clients * Fully typed requests and responses (`py.typed`) * Python 3.9+ Source code is on [GitHub](https://github.com/FormableDocs/formable-python). ## Installation ```bash pip theme={null} pip install formable-sdk ``` ```bash uv theme={null} uv add formable-sdk ``` ```bash poetry theme={null} poetry add formable-sdk ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```python theme={null} import os from formable import Formable formable = Formable(api_key=os.environ["FORMABLE_API_KEY"]) ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). ```python theme={null} with open("nda.docx", "rb") as f: result = formable.templates.create( file=f.read(), filename="nda.docx", signer_roles=[ {"name": "Client", "order": 0}, {"name": "Witness", "order": 1}, ], ) template_id = result["templateId"] # Mint a fresh edit URL later (expires after 1 day) edit = formable.templates.create_edit_url(template_id) print(edit["editUrl"], edit["expiresAt"]) ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```python theme={null} # Formable emails each signer a signing link request = formable.signature_requests.create( template_id=template_id, signers=[ {"email": "jane@example.com", "name": "Jane Doe", "role": "Client"}, {"email": "bob@example.com", "name": "Bob Smith", "role": "Witness"}, ], ) # Embedded flow: mint signing URLs to embed in an iframe yourself embedded = formable.signature_requests.create_embedded( template_id=template_id, signers=[{"email": "jane@example.com", "name": "Jane Doe", "role": "Client"}], test_mode=True, ) signer = embedded["signers"][0] signing = formable.signature_requests.create_signing_url( signer["recipientSignatureId"] ) # Track progress from datetime import datetime, timezone current = formable.signature_requests.get(embedded["signatureRequestId"]) all_requests = formable.signature_requests.list( updated_since=datetime(2026, 1, 1, tzinfo=timezone.utc) ) events = formable.signature_requests.get_events(embedded["signatureRequestId"]) # Download the signed document once completed envelope = formable.signature_requests.get_signed_envelope( embedded["signatureRequestId"] ) print(envelope["signedEnvelopePresignedUrl"]) ``` `get_signed_envelope` raises a `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `get` until `status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```python theme={null} created = formable.redline_requests.create( template_id=template_id, members=[ {"email": "us@example.com", "display_name": "John Doe", "role": "DisclosingParty"}, {"email": "them@example.com", "display_name": "Jane Smith", "role": "ReceivingParty"}, ], metadata={"subject": "Mutual NDA"}, ) redline_request_id = created["redlineRequestId"] # Mint a redline URL for a member (embed in an iframe) url = formable.redline_requests.create_url(redline_request_id, "them@example.com") # Manage members and track progress formable.redline_requests.update_members( redline_request_id, [{"email": "counsel@example.com", "display_name": "Counsel", "role": "ReceivingCounsel"}], ) redline = formable.redline_requests.get(redline_request_id) events = formable.redline_requests.get_events(redline_request_id) ``` ## Billing and health ```python theme={null} billing = formable.billing() print(billing["numberOfRedliningSessions"]) health = formable.health() ``` ## Async client Every method is also available on `AsyncFormable` with the same signatures. ```python theme={null} import asyncio from formable import AsyncFormable async def main(): async with AsyncFormable(api_key=os.environ["FORMABLE_API_KEY"]) as formable: health = await formable.health() asyncio.run(main()) ``` ## Error handling All non-2xx responses raise a `FormableError` with the server's error message, HTTP status, and parsed response body. ```python theme={null} from formable import FormableError try: formable.signature_requests.get("missing-id") except FormableError as error: print(error.status, error) ``` ## Configuration | Option | Description | Default | | ---------- | --------------------------------------------------------- | --------------------------------- | | `api_key` | Your Formable API key (sent as a bearer token). Required. | - | | `base_url` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `client` | Custom `httpx.Client` (or `httpx.AsyncClient` for async). | Built-in client with 60s timeout | ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Ruby Source: https://docs.formabledocs.com/sdks/ruby Install and use formable, the official Ruby SDK for the Formable API. [`formable`](https://rubygems.org/gems/formable) is the official Ruby SDK for the Formable API (v1). It covers templates, signature requests, redlining, and billing. * Faraday HTTP client (injectable) * Keyword arguments and snake\_case method names * Ruby 3.1+ Source code is on [GitHub](https://github.com/FormableDocs/formable-ruby). ## Installation ```ruby Gemfile theme={null} gem "formable" ``` ```bash bundle theme={null} bundle add formable ``` ```bash gem theme={null} gem install formable ``` ## Initialize the client Pass your API key from [Settings](https://app.formabledocs.com/settings). It's sent as a bearer token on every request. ```ruby theme={null} require "formable" formable = Formable.new(api_key: ENV.fetch("FORMABLE_API_KEY")) ``` ## Templates Upload a document to create a reusable template, then mint edit URLs to place fields in the embedded editor. See the [embedded templates walkthrough](/walkthroughs/embedded-templates). `file` accepts a path, binary string, or IO. Pass `filename:` when `file` is not a path. ```ruby theme={null} result = formable.templates.create( file: "nda.docx", signer_roles: [ { name: "Client", order: 0 }, { name: "Witness", order: 1 } ] ) template_id = result["templateId"] # Mint a fresh edit URL later (expires after 1 day) edit = formable.templates.create_edit_url(template_id) ``` ## Signature requests Send a template for signing by email, or embed the signing experience in your product. See the [embedded signing walkthrough](/walkthroughs/embedded-signing). ```ruby theme={null} # Formable emails each signer a signing link request = formable.signature_requests.create( template_id: template_id, signers: [ { email: "jane@example.com", name: "Jane Doe", role: "Client" }, { email: "bob@example.com", name: "Bob Smith", role: "Witness" } ] ) # Embedded flow: mint signing URLs to embed in an iframe yourself embedded = formable.signature_requests.create_embedded( template_id: template_id, signers: [{ email: "jane@example.com", name: "Jane Doe", role: "Client" }], test_mode: true ) signer = embedded["signers"].first signing = formable.signature_requests.create_signing_url( signer["recipientSignatureId"] ) # Track progress current = formable.signature_requests.get(embedded["signatureRequestId"]) all_requests = formable.signature_requests.list( updated_since: Time.utc(2026, 1, 1) ) events = formable.signature_requests.get_events(embedded["signatureRequestId"]) # Download the signed document once completed envelope = formable.signature_requests.get_signed_envelope( embedded["signatureRequestId"] ) ``` `get_signed_envelope` raises a `409` until the document is complete. Wait for the [`document_completed`](/webhooks/events#document_completed) webhook, or poll `get` until `status` is `Completed`. ## Redline requests Run turn-based contract negotiation before signing. See the [redlining walkthrough](/walkthroughs/redlining). ```ruby theme={null} created = formable.redline_requests.create( template_id: template_id, members: [ { email: "us@example.com", display_name: "John Doe", role: "DisclosingParty" }, { email: "them@example.com", display_name: "Jane Smith", role: "ReceivingParty" } ], metadata: { subject: "Mutual NDA" } ) redline_request_id = created["redlineRequestId"] # Mint a redline URL for a member (embed in an iframe) url = formable.redline_requests.create_url(redline_request_id, "them@example.com") # Manage members and track progress formable.redline_requests.update_members( redline_request_id, [{ email: "counsel@example.com", display_name: "Counsel", role: "ReceivingCounsel" }] ) redline = formable.redline_requests.get(redline_request_id) events = formable.redline_requests.get_events(redline_request_id) ``` ## Billing and health ```ruby theme={null} billing = formable.billing health = formable.health ``` ## Error handling All non-2xx responses raise a `Formable::Error` with the server's error message, HTTP status, and parsed response body. ```ruby theme={null} begin formable.signature_requests.get("missing-id") rescue Formable::Error => error warn "#{error.status} #{error.message}" end ``` ## Configuration The client takes an API key, then optional `base_url`, `timeout`, and Faraday `connection` arguments. ```ruby theme={null} require "faraday" formable = Formable.new( api_key: ENV.fetch("FORMABLE_API_KEY"), base_url: "https://api.formabledocs.com/v1", timeout: 60, connection: Faraday.new { |conn| conn.adapter Faraday.default_adapter } ) ``` | Option | Description | Default | | ------------ | --------------------------------------------------------- | --------------------------------- | | `api_key` | Your Formable API key (sent as a bearer token). Required. | - | | `base_url` | Override the API base URL. | `https://api.formabledocs.com/v1` | | `timeout` | Per-request timeout in seconds. | `60` | | `connection` | Custom `Faraday::Connection`. | Built-in client with 60s timeout | Request hashes accept snake\_case keys (`template_id`, `display_name`, `field_id`). Responses use the API's camelCase field names (`templateId`, `displayName`). ## Next steps Go from a plain document to a signed PDF in 4 calls. Every endpoint the SDK wraps, with a live playground. # Embedded signing Source: https://docs.formabledocs.com/walkthroughs/embedded-signing Let users sign documents inside your app with an embeddable signing URL. Give your users the ability to sign documents directly in your product. Formable returns a short-lived signing URL that you load in an iframe — no email round-trip required. This walkthrough covers the full embedded signing flow: create a signature request on your server, embed the signing experience on the client, detect completion, and download the signed PDF. Need a shorter path first? Complete the [quickstart](/quickstart), then return here for the full integration pattern. Prefer email delivery instead of an iframe? See [Non-embedded signing](/walkthroughs/non-embedded-signing). Walkthroughs use cURL. The same calls are in the [official SDKs](/sdks/overview). ## Prerequisites Before you begin: 1. Create an organization and [API key](/authentication) in [Settings](https://app.formabledocs.com/settings). 2. Upload a template, place at least one required signature field, and assign it a signer role — see the [Embedded templates walkthrough](/walkthroughs/embedded-templates). 3. Call the Formable API from your **backend** only. Never expose your API key in the browser. ## Overview Start a request from a template with one or more signers (each with a role). Mint a short-lived URL for a signer's `recipientSignatureId` just before they are ready. Load the signing experience inside your product. Listen for webhooks or poll status, then fetch the signed PDF. ## Server side Create the signature request and signing URL on your backend, then pass only the URL to the client. ### 1. Create an embedded signature request Start from a template you've already prepared. An embedded signature request needs: * `templateId` — which document to send * `signers` — who will sign (`email`, `name`, and `role` for every role used by required fields) `sender` is optional. When omitted, Formable uses the API key organization owner. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/signature-requests/embedded \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "signers": [ { "email": "signer@example.com", "name": "Jane Doe", "role": "Client" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "testMode": true }' ``` ```json Response theme={null} { "signatureRequestId": "sr_456def", "templateId": "abc123xyz", "signers": [ { "email": "signer@example.com", "name": "Jane Doe", "recipientSignatureId": "rsig_789ghi" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "status": "Created", "testMode": true } ``` Save the `signatureRequestId` for status and download. Save each signer's `recipientSignatureId` to mint their signing URL. #### Prefill fields Templates can include fields beyond the signature — company name, dates, checkboxes, and so on. Pass a `fields` array to set values before the signer opens the document. Each entry sets a `value` on a `fieldId` from the template. ```json theme={null} { "templateId": "abc123xyz", "signers": [ { "email": "signer@example.com", "name": "Jane Doe", "role": "Client" } ], "sender": { "email": "you@yourcompany.com", "name": "Your Company" }, "fields": [ { "fieldId": "field_company_name", "value": "Acme Corporation" }, { "fieldId": "field_effective_date", "value": "2024-02-01" } ], "testMode": true } ``` Field IDs come from the template. Open the template's [editor URL](/walkthroughs/embedded-templates) to place fields and copy their IDs. A `fieldId` that doesn't exist on the template returns a `400`. #### Test mode Set `testMode: true` while integrating so the request doesn't count toward billing. Test mode documents are watermarked and are not legally binding. ### 2. Generate a signing URL A **signing URL** opens the signing experience for one recipient. Create it with that signer's `recipientSignatureId`. It expires **one hour** after creation, so generate it right before you show it to the signer. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/recipient-signatures/rsig_789ghi/url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signingUrl": "https://app.formabledocs.com/sign/embedded/xyz789abc", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` Creating a signing URL for an already completed request returns `409`. ## Client side Embed the `signingUrl` in an iframe so the signer can complete the document without leaving your product. ```html theme={null} ``` Request the URL from your backend and pass it to the page just before rendering: ```javascript React theme={null} function SigningFrame({ recipientSignatureId }) { const [signingUrl, setSigningUrl] = useState(null); useEffect(() => { fetch(`/api/formable/signing-url?recipientSignatureId=${recipientSignatureId}`) .then((res) => res.json()) .then((data) => setSigningUrl(data.signingUrl)); }, [recipientSignatureId]); if (!signingUrl) return

Loading…

; return ( ```
Never call the Formable API with your token from the browser. Generate the signing URL on your server and hand only the resulting URL to the client. Give the iframe enough height (around `800px`) so signers don't have to scroll within a small frame. Regenerate the URL if the signer returns after it has expired. ## Signing experience Once the iframe loads, the signer walks through required fields on the document. Click **Start** to begin. Required fields are highlighted so the signer knows where to act. Embedded signing view showing document.docx with a Sign here field on the Customer line, a Required callout, and a Start button When the signer reaches a Signature field, they can **Draw**, **Type**, or use a **Saved** signature. They must agree to the terms and conditions before confirming. Signature modal over the document with Draw, Type, and Saved tabs, a drawn signature, consent checkbox, and Confirm button After all required fields are complete, the signer reviews the document and clicks **Finish**. That completes the signature request and fires `onSigningComplete` to the parent window. Embedded signing view with a completed Customer signature and a Finish button ## Detect completion When the signer finishes, the signature request `status` becomes `Completed`. You have four ways to learn that: When signing finishes inside the embedded iframe, Formable posts a message to the parent window. Use this to close the iframe or update your UI immediately: ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; switch (event.data.type) { case "onSigningComplete": // Signer finished — close the iframe or show a success state break; case "onSigningError": // Signing failed to complete — show an error or retry break; } }); ``` | Message `type` | When it fires | | ------------------- | ----------------------------------------------- | | `onSigningComplete` | The signer successfully completed the document. | | `onSigningError` | Signing could not be completed from the iframe. | Always check `event.origin`. Treat these as client UX signals — confirm completion with a webhook or API poll before downloading the signed PDF. Configure a [webhook endpoint](/webhooks/overview) and handle the [`document_completed`](/webhooks/events#document_completed) event. Formable pushes this once the signed PDF is ready, so you can download without polling. Do not use [`document_signed`](/webhooks/events#document_signed) alone — that fires when a signer finishes, before the completed file is available. ```json theme={null} { "event": { "event_type": "document_completed", "event_category": "signing" }, "signing": { "signature_request_id": "sr_456def" } } ``` Fetch the [signature request](/api-reference/endpoint/get-signature-request) and check `status`. It moves from `Created` to `Completed`. The response also includes `signers` (with `recipientSignatureId`) and envelope `fields`. ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def \ --header "Authorization: Bearer $TOKEN" ``` Fetch [signature request events](/api-reference/endpoint/get-signature-request-events) for a chronological history of what happened. ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/events \ --header "Authorization: Bearer $TOKEN" ``` Use iframe `postMessage` for immediate UI updates, and prefer the `document_completed` webhook as the source of truth before downloading. Use [List signature requests](/api-reference/endpoint/list-signature-requests) with `updatedSince`, or the event stream, to reconcile anything you missed. ## Download the signed PDF Once status is `Completed`, fetch a temporary download URL for the signed PDF (the **signed envelope**). ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` Open or download that URL promptly — it is a short-lived presigned link. The signed PDF includes an audit trail with timestamps, actors, and IP addresses for each step. Formable audit trail page for document.docx with Envelope ID, Completed status, and a Document History of Created, Sent, Signed, and Completed events Requesting the signed envelope before signing is complete returns `409` with `"Envelope has not been signed yet"`. ## Next steps Upload documents and place signature and form fields. Get notified when a document is viewed or signed. Negotiate a DOCX contract before signing. Full endpoint docs for signature requests. # Embedded templates Source: https://docs.formabledocs.com/walkthroughs/embedded-templates Create templates in your product for use with the signing API. Upload a file, embed the editor, then send with a signature request. Give your users the ability to upload documents and place signature fields directly in your product. Formable returns a short-lived `editUrl` that you load in an iframe — the same embedding pattern as [embedded signing](/walkthroughs/embedded-signing). Templates you create here are what you pass to the [signing API](/walkthroughs/embedded-signing). After the user places fields and saves, reuse the `templateId` to create signature requests (ie let users sign the template). This walkthrough covers uploading a file, embedding the template editor, placing fields, and regenerating an editor URL when it expires. Walkthroughs use cURL. The same calls are in the [official SDKs](/sdks/overview). ## Prerequisites * A [developer account](https://docs.formabledocs.com/developer-account) * An [API key](/authentication) from [Settings](https://app.formabledocs.com/settings) * A PDF or Word document under 40MB * Call the Formable API from your **backend** only ## Supported files | Format | MIME type | | ------ | ------------------------------------------------------------------------- | | PDF | `application/pdf` | | DOCX | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | | DOC | `application/msword` | Maximum size: **40MB**. If you later use [redlining](/walkthroughs/redlining), the template needs a **DOCX** source file because redlining edits the underlying document. ## Overview Send the file to Formable and receive a `templateId` plus an `editUrl`. Load the `editUrl` in an iframe so users place fields in your product. Pass the `templateId` into a [signature request](/walkthroughs/embedded-signing) (or a redline request) to collect signatures. The embedded editor prepares the document. The [embedded signing](/walkthroughs/embedded-signing) walkthrough is how you send that same `templateId` for signature. ## Server side ### 1. Upload a template Send the file as `multipart/form-data` with a `file` part and a `filename` field. Formable returns the `templateId` plus a ready-to-use editor URL. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates \ --header "Authorization: Bearer $TOKEN" \ --form 'file=@./agreement.docx' \ --form 'filename=agreement.docx' \ --form 'signer_roles=[{"name":"Client","order":0}]' ``` ```json Response theme={null} { "templateId": "abc123xyz", "editTemplateAccess": { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } } ``` Save the `templateId`. This is the ID you pass to the signing API when you [create a signature request](/walkthroughs/embedded-signing) (and when you create redline requests). `signer_roles` is optional. Pass it as a JSON string in multipart form data when you want named roles (for example `Client`, `Witness`) before opening the editor. You'll assign those roles to required fields in the editor, then pass matching `signers[].role` values when creating a signature request. Files over 40MB return `413`. Unsupported file types return `400` with `"Unsupported file type, only pdf, docx, and doc are supported"`. ### 2. Regenerate an editor URL Editor URLs expire (by default one day after creation). Mint a fresh one when a user needs to edit the template again. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/templates/abc123xyz/edit-url \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "editUrl": "https://app.formabledocs.com/template-setup/abc123xyz", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` Generate the URL just before you show the editor. Pass only the URL to the client — never your API key. You can only create an edit URL for templates owned by your organization. Other templates return `403`, and unknown IDs return `404`. ## Client side Embed the `editUrl` in an iframe so users place fields without leaving your product. ```html theme={null} ``` Request the URL from your backend and pass it to the page just before rendering: ```javascript React theme={null} function TemplateEditorFrame({ templateId }) { const [editUrl, setEditUrl] = useState(null); useEffect(() => { fetch(`/api/formable/edit-url?templateId=${templateId}`) .then((res) => res.json()) .then((data) => setEditUrl(data.editUrl)); }, [templateId]); if (!editUrl) return

Loading…

; return ( ```
### Listen for editor events When the user saves in the embedded editor, Formable posts a message to the parent window. Listen for it to close the iframe or advance your UI: ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; switch (event.data.type) { case "onTemplateEditorSaved": // Template saved — close the iframe or move to the next step break; case "onTemplateEditorError": // Save failed to notify the parent — show an error or retry break; } }); ``` | Message `type` | When it fires | | ----------------------- | ----------------------------------------------------------------- | | `onTemplateEditorSaved` | The user successfully saved the template in the editor. | | `onTemplateEditorError` | The editor could not deliver the save notification to the parent. | Always check `event.origin` before handling a message. These events are for client UX only — they are not a substitute for verifying template state on your backend before sending. ## Place fields in the editor In the embedded editor, use the **Field selector** to place and configure fields on the document. Template editor showing document.docx with the Field selector sidebar listing Signature, Text, and other field types | Field | Use | | ------------- | ---------------------- | | **Signature** | Where the signer signs | | **Text** | Free-form text input | | **Date** | A date value | | **Checkbox** | A boolean toggle | | **Amount** | A monetary value | Select a placed field to set whether it's required, assign a **signer role**, and copy its **Reference ID**. Required fields must have a role before you can create a signature request. You can reference field IDs if you decide to [prefill fields](/walkthroughs/embedded-signing#prefill-fields) on a signature request. Template editor with a Signature field selected over the Customer line, showing Required toggle and Reference ID in the sidebar (Optional) Click **Preview** to walk through the document as a signer would see it, including required fields. Template preview showing a required Sign here field on the Customer signature line When you're done, click **Save template**. The editor posts `onTemplateEditorSaved` to the parent window, which you can process to close the iframe. ## Next steps Your template is ready for the signing API. Pass the `templateId` from this walkthrough into a signature request to collect signatures. Use this template with the signing API — create a signature request and embed the signing experience. Negotiate revisions on a DOCX template before signing. # Non-embedded signing Source: https://docs.formabledocs.com/walkthroughs/non-embedded-signing Send a document for signature by email. Formable delivers the signing link. Send a document for signature without embedding Formable in your product. You create a regular signature request with one or more signers, and Formable emails each person a signing link. Use this flow when signers should complete the document in their browser from an email. Prefer signing inside your product? See [Embedded signing](/walkthroughs/embedded-signing) instead. Walkthroughs use cURL. The same calls are in the [official SDKs](/sdks/overview). ## Prerequisites Before you begin: 1. Create an organization and [API key](/authentication) in [Settings](https://app.formabledocs.com/settings). 2. Upload a template and place at least one signature field — see the [Embedded templates walkthrough](/walkthroughs/embedded-templates). 3. Call the Formable API from your **backend** only. Never expose your API key in the browser. ## Overview Start a request from a template with one or more signers. Formable emails each signer automatically. The signer opens the link from email and fills required fields on Formable. Listen for webhooks or poll status, then fetch the signed PDF. ## Create a regular signature request Call this from your backend when you're ready to send. A regular signature request needs: * `templateId` — which document to send * `signers` — who will sign (`email` + `name`), in signing order Unlike [embedded signature requests](/api-reference/endpoint/create-signature-request), you do **not** generate a signing URL. Formable creates the shares and emails each signer for you. See [Create a regular signature request](/api-reference/endpoint/create-regular-signature-request). ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/signature-requests \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "role": "Client" }, { "email": "bob@example.com", "name": "Bob Smith", "role": "Witness" } ], "testMode": true }' ``` ```json Response theme={null} { "signatureRequestId": "sr_456def", "templateId": "abc123xyz", "signers": [ { "email": "jane@example.com", "name": "Jane Doe", "recipientSignatureId": "rsig_jane123" }, { "email": "bob@example.com", "name": "Bob Smith", "recipientSignatureId": "rsig_bob456" } ], "sender": { "email": "owner@example.com", "name": "Org Owner" }, "status": "Created", "testMode": true } ``` Save the `signatureRequestId`. You'll use it to track progress and download the signed PDF. Each signer receives an email with a link to `/sign/{envelopeShareId}` — the same style of link used when you share from the Formable app. ### Test mode Set `testMode: true` while integrating so the request doesn't count toward billing. Test mode documents are watermarked and are not legally binding. ## Signing experience Signers open the link from their email and complete required fields on Formable. Click **Start** to begin. Required fields are highlighted so the signer knows where to act. Signing view showing document.docx with a Sign here field on the Customer line, a Required callout, and a Start button When the signer reaches a Signature field, they can **Draw**, **Type**, or use a **Saved** signature. They must agree to the terms and conditions before confirming. Signature modal over the document with Draw, Type, and Saved tabs, a drawn signature, consent checkbox, and Confirm button After all required fields are complete, the signer reviews the document and clicks **Finish**. Signing view with a completed Customer signature and a Finish button ## Detect completion When signing finishes, the signature request `status` becomes `Completed`. You have three ways to learn that: Configure a [webhook endpoint](/webhooks/overview) and handle the [`document_completed`](/webhooks/events#document_completed) event. Formable pushes this once the signed PDF is ready, so you can download without polling. Do not use [`document_signed`](/webhooks/events#document_signed) alone — that fires when a signer finishes, before the completed file is available. ```json theme={null} { "event": { "event_type": "document_completed", "event_category": "signing" }, "signing": { "signature_request_id": "sr_456def" } } ``` Fetch the [signature request](/api-reference/endpoint/get-signature-request) and check `status`. It moves from `Created` to `Completed`. The response also includes `signers` (with `recipientSignatureId`) and envelope `fields`. ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def \ --header "Authorization: Bearer $TOKEN" ``` Fetch [signature request events](/api-reference/endpoint/get-signature-request-events) for a chronological history of what happened. ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/events \ --header "Authorization: Bearer $TOKEN" ``` Prefer the `document_completed` webhook as the source of truth before downloading. Use [List signature requests](/api-reference/endpoint/list-signature-requests) with `updatedSince`, or the event stream, to reconcile anything you missed. ## Download the signed PDF Once status is `Completed`, fetch a temporary download URL for the signed PDF (the **signed envelope**). ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/signature-requests/sr_456def/signed-envelope \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "signedEnvelopePresignedUrl": "https://s3.amazonaws.com/bucket/signed-envelope.pdf?..." } ``` Open or download that URL promptly — it is a short-lived presigned link. Requesting the signed envelope before signing is complete returns `409` with `"Envelope has not been signed yet"`. ## Embedded vs non-embedded | | Embedded | Non-embedded | | -------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | | Endpoint | [`POST /v1/signature-requests/embedded`](/api-reference/endpoint/create-signature-request) | [`POST /v1/signature-requests`](/api-reference/endpoint/create-regular-signature-request) | | Delivery | You generate a [signing URL](/api-reference/endpoint/create-signing-url) and embed it | Formable emails each signer | | UI | Iframe in your product | Formable hosted page from email link | | Best for | In-app signing flows | Email-based signing outside your app | ## Next steps Embed the signing experience in an iframe inside your product. Map `send` / `send_with_template` to Formable. Get notified when a document is viewed or signed. Request and response schema for `POST /v1/signature-requests`. # Embedded redlining Source: https://docs.formabledocs.com/walkthroughs/redlining Run a turn-based contract negotiation between two parties inside your product. Redlining lets two parties negotiate revisions to a DOCX contract in turns. You create a redline request on your server, then embed a member-specific redline URL in an iframe so each party can revise on their turn. This walkthrough covers creating a redline request, managing members, embedding redline URLs, and tracking the negotiation until the document is ready to sign. Redlining edits the underlying document, so the template **must have a DOCX source file**. Creating a redline request for a template without one returns `404`. Most integrations only need [embedded signing](/walkthroughs/embedded-signing) — use redlining when you need negotiation before signature. Walkthroughs use cURL. The same calls are in the [official SDKs](/sdks/overview). ## Prerequisites 1. An [API key](/authentication) from [Settings](https://app.formabledocs.com/settings) 2. A [template](/walkthroughs/embedded-templates) uploaded from a **DOCX** file ## Roles A redline request has members, each with a role. The two core parties are set up front; counsel can be added later. | Role | Description | | ------------------- | --------------------------------- | | `DisclosingParty` | Originated the contract. | | `ReceivingParty` | Requests changes to the contract. | | `DisclosingCounsel` | Helps the disclosing party. | | `ReceivingCounsel` | Helps the receiving party. | ## Server side ### 1. Create a redline request Provide the `templateId` and at least one member. Each member needs an `email`, `displayName`, and `role`. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/redline-requests \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "templateId": "abc123xyz", "members": [ { "email": "legal@acme.com", "displayName": "Acme Legal", "role": "DisclosingParty" }, { "email": "counsel@client.com", "displayName": "Client Counsel", "role": "ReceivingParty" } ], "metadata": { "subject": "Mutual NDA" }, "testMode": true }' ``` ```json Response theme={null} { "redlineRequestId": "rr_789ghi", "templateId": "def456uvw" } ``` The response `templateId` is a **new** template created for this negotiation — a copy that receives the redlining changes. Your original template is left untouched. The optional `metadata.subject` sets a subject line for the negotiation. Set `testMode: true` to keep the request out of billing while you integrate. Test mode documents are watermarked and are not legally binding. ### 2. Manage members Add or update members at any time — for example, to invite counsel — with [Update redline members](/api-reference/endpoint/put-redline-members). This replaces the member list, so send the full set. ```bash theme={null} curl --request PUT \ --url https://api.formabledocs.com/v1/redline-requests/rr_789ghi/members \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "members": [ { "email": "legal@acme.com", "displayName": "Acme Legal", "role": "DisclosingParty" }, { "email": "counsel@client.com", "displayName": "Client Counsel", "role": "ReceivingParty" }, { "email": "outside@lawfirm.com", "displayName": "Outside Counsel", "role": "ReceivingCounsel" } ] }' ``` ### 3. Generate a redline URL for a member Each member works on their turn through a member-specific redline URL. Pass the member's email — they must already be part of the request. Generate the URL just-in-time; it is short-lived. ```bash theme={null} curl --request POST \ --url https://api.formabledocs.com/v1/redline-requests/rr_789ghi/url \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ "memberEmail": "counsel@client.com" }' ``` ```json Response theme={null} { "redlineUrl": "https://app.formabledocs.com/redlining/embedded/xyz789abc", "expiresAt": "2024-01-16T10:30:00.000Z" } ``` ## Client side Embed the `redlineUrl` in an iframe for that member: ```html theme={null} ``` Generate the URL on your backend and pass only the URL to the client — the same pattern as [embedded signing](/walkthroughs/embedded-signing#client-side). If it isn't the member's turn, the URL opens in a read-only "out of turn" view. Regenerate the URL if the member returns after it has expired. ### Listen for editor events The redline editor posts messages to the parent window so you can close the iframe or advance your UI: ```javascript theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.formabledocs.com") return; if (!event.data || typeof event.data !== "object") return; switch (event.data.type) { case "onRedlineEditorSaved": // Turn submitted or review finished — close the iframe break; case "onRedlineEditorError": // Save failed — show an error or retry break; case "onRedlineEditorClosed": // User confirmed exit without saving break; case "userRequestedToSign": // User chose to proceed to signing without further revisions break; } }); ``` | Message `type` | When it fires | | ----------------------- | --------------------------------------------------------------- | | `onRedlineEditorSaved` | The user submitted their turn or finished review. | | `onRedlineEditorError` | The editor could not complete the save/submit. | | `onRedlineEditorClosed` | The user closed the editor (for example after confirming exit). | | `userRequestedToSign` | The user chose to sign without requesting further changes. | Always check `event.origin`. Use these for client UX; confirm negotiation state with the API or [webhooks](/webhooks/overview). ## Track the negotiation The `currentRound` field indicates whose turn it is (`Disclosing` or `Receiving`), and `status` tracks progress through the negotiation. ```bash theme={null} curl --request GET \ --url https://api.formabledocs.com/v1/redline-requests/rr_789ghi \ --header "Authorization: Bearer $TOKEN" ``` ```json Response theme={null} { "templateId": "def456uvw", "status": "ReceivingPartyRequestedReview", "currentRound": "Disclosing", "testMode": false, "members": [ { "role": "DisclosingParty", "email": "legal@acme.com", "displayName": "Acme Legal" }, { "role": "ReceivingParty", "email": "counsel@client.com", "displayName": "Client Counsel" } ] } ``` | Status | Meaning | | -------------------------------- | -------------------------------------------------- | | `ReceivingPartyOpened` | The receiving party opened the document. | | `ReceivingPartyDraft` | The receiving party is drafting revisions. | | `ReceivingPartyRequestedReview` | Revisions sent to the disclosing party for review. | | `DisclosingPartyDraft` | The disclosing party is drafting revisions. | | `DisclosingPartyRequestedReview` | Revisions sent to the receiving party for review. | | `DocumentReadyForSigning` | All changes resolved; ready to sign. | For a full history of turn changes and review requests, read [redline request events](/api-reference/endpoint/get-redline-request-events). To sync many negotiations, use [List redline requests](/api-reference/endpoint/list-redline-requests) with `updatedSince`. You can also listen for redlining events on your [webhook endpoint](/webhooks/overview). ## Ready for signing When the negotiation reaches `DocumentReadyForSigning`, send the finalized template through the [embedded signing](/walkthroughs/embedded-signing) flow using the redline request's `templateId`. ## Next steps Collect signatures on the finalized template. Get notified as negotiation status changes. # Event catalog Source: https://docs.formabledocs.com/webhooks/events Every webhook event type and its payload schema. This page lists every event Formable sends to your webhook endpoint. Most integrations only need **signing** events (`document_viewed`, `document_signed`, `document_completed`). **Redlining** events are listed below for the optional negotiation workflow. Every payload shares the same top-level shape — an `event` metadata object plus a category-specific object: ```json theme={null} { "event": { "event_id": "string", "event_category": "signing | redlining", "event_type": "string", "event_time": 1777039156023 }, "signing": { } } ``` Unique identifier for this delivery. Use it for idempotency. Either `signing` or `redlining`. The specific event type, listed below. When the event occurred, in **milliseconds** since the Unix epoch. *** ## Signing events Signing events carry a `signing` object and reference a [signature request](/walkthroughs/embedded-signing). The ID of the signature request the event relates to. ### `document_viewed` The signer opened the document for the first time. ```json theme={null} { "event": { "event_id": "p-1phZC6_oU8qS191gsLT", "event_category": "signing", "event_type": "document_viewed", "event_time": 1777039156023 }, "signing": { "signature_request_id": "YfdkOH7dVtaGzl-egS6NI" } } ``` ### `document_signed` A single signer completed and signed the document. This does not mean the signed PDF is ready yet — wait for [`document_completed`](#document_completed) before fetching the [signed envelope](/api-reference/endpoint/get-signed-envelope). The ID of the recipient who signed. Matches the `recipientSignatureId` returned when you [create a signature request](/api-reference/endpoint/create-signature-request). ```json theme={null} { "event": { "event_id": "p-1phZC6_oU8qS191gsLT", "event_category": "signing", "event_type": "document_signed", "event_time": 1777039156023 }, "signing": { "signature_request_id": "YfdkOH7dVtaGzl-egS6NI", "recipient_signature_id": "rsig_789ghi" } } ``` ### `document_completed` All required fields are filled, the signature request is complete, and the signed PDF (with audit trail) is ready to download. If there are multiple signers, this means all signers have signed. Wait for this event before calling [Get signed envelope](/api-reference/endpoint/get-signed-envelope). ```json theme={null} { "event": { "event_id": "p-1phZC6_oU8qS191gsLT", "event_category": "signing", "event_type": "document_completed", "event_time": 1777039156023 }, "signing": { "signature_request_id": "YfdkOH7dVtaGzl-egS6NI" } } ``` *** ## Redlining events Redlining events carry a `redlining` object and reference a [redline request](/walkthroughs/redlining). Every redlining payload includes these base fields, plus any event-specific fields listed below. The ID of the redline request the event relates to. The role of the member who triggered the event. One of `disclosing_party`, `receiving_party`, `disclosing_counsel`, or `receiving_counsel`. ### `redline_edit_inserted` A member inserted new text into the document. The inserted text. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_edit_inserted", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "disclosing_party", "redline_edit_insertion": "including any amendments hereto" } } ``` ### `redline_edit_deleted` A member deleted text from the document. The deleted text. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_edit_deleted", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "receiving_party", "redline_edit_deletion": "within thirty (30) days" } } ``` ### `redline_edit_accepted` A member accepted a tracked change proposed by the other party. The content of the change that was accepted. The kind of change. One of `inserted`, `deleted`, `replaced`, or `formatted`. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_edit_accepted", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "disclosing_party", "content": "net sixty (60) days", "change_type": "replaced" } } ``` ### `redline_edit_rejected` A member rejected a tracked change proposed by the other party. The content of the change that was rejected. The kind of change. One of `inserted`, `deleted`, `replaced`, or `formatted`. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_edit_rejected", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "receiving_party", "content": "exclusive jurisdiction of the courts of Delaware", "change_type": "inserted" } } ``` ### `redline_comment_added` A member added a comment to the document. The comment text. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_comment_added", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "receiving_party", "comment_added": "Can we clarify the definition of Confidential Information here?" } } ``` ### `redline_message_added` A member posted a message in the negotiation thread. The message text. The email address of the message author. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_message_added", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "disclosing_party", "message": "Please review the liability cap.", "author_email": "counsel@example.com" } } ``` ### `redline_turn_ended` A member finished their turn and passed the document to the other party. Only the base `redlining` fields are included. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_turn_ended", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "receiving_party" } } ``` ### `document_ready_for_signing` All tracked changes have been resolved and the document is ready to be sent for signature. Only the base `redlining` fields are included. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "document_ready_for_signing", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "disclosing_party" } } ``` ### `redline_shared` A redline request was shared with a member. Only the base `redlining` fields are included. ```json theme={null} { "event": { "event_id": "n9c8123umxrs082rum", "event_category": "redlining", "event_type": "redline_shared", "event_time": 1777039156023 }, "redlining": { "redline_request_id": "92cn47yxm92smsmdw", "redline_member_role": "disclosing_party" } } ``` *** ## Handling events Switch on `event.event_type` to route each event. Unknown types should be ignored gracefully so new event types don't break your handler. ```javascript theme={null} switch (payload.event.event_type) { case "document_completed": await onDocumentCompleted(payload.signing.signature_request_id); break; case "document_ready_for_signing": await onReadyForSigning(payload.redlining.redline_request_id); break; // ...handle other types you care about default: // Ignore unrecognized event types. break; } ``` # Webhooks overview Source: https://docs.formabledocs.com/webhooks/overview Receive real-time notifications when documents are viewed or signed. Webhooks let Formable notify your application the moment something happens — for example a document is viewed or signed — without you having to poll the API. When an event occurs, Formable sends an HTTP `POST` request to a URL you provide. Prefer webhooks over polling for reacting to activity in near real time. You can still use [signature request events](/api-reference/endpoint/get-signature-request-events) to reconcile or backfill. If you use redlining, the same webhook endpoint also receives redlining events. ## How delivery works When an event fires, Formable sends a single HTTP request to your endpoint: | Property | Value | | ----------------- | ------------------------------------------------------- | | Method | `POST` | | Content-Type | `application/json` | | Body | The [event payload](/webhooks/events) as JSON | | Signature header | `Content-Sha256` — a base64 HMAC-SHA256 of the raw body | | Timeout | 10 seconds | | Expected response | HTTP `200` | The request body **is** the event object — it is not wrapped in an envelope. Every payload has the same top-level shape: ```json theme={null} { "event": { "event_id": "p-1phZC6_oU8qS191gsLT", "event_category": "signing", "event_type": "document_completed", "event_time": 1777039156023 }, "signing": { "signature_request_id": "YfdkOH7dVtaGzl-egS6NI" } } ``` * `event.event_id` — a unique ID for this delivery. Use it for [idempotency](#idempotency). * `event.event_category` — `signing` or `redlining`. * `event.event_type` — the specific event, for example `document_completed`. See the [event catalog](/webhooks/events). * `event.event_time` — when the event occurred, in **milliseconds** since the Unix epoch. * A category-specific object (`signing` or `redlining`) carries the event details. ## Register an endpoint Register your webhook URL in [Settings](https://app.formabledocs.com/settings) after creating an organization. Each organization has one webhook URL. For detailed steps, see [Set up a developer account](/developer-account#register-a-webhook). Go to [Settings](https://app.formabledocs.com/settings) and create an organization if you don't have one yet. In the **Webhooks** section, click **Register webhook**, enter the HTTPS URL that should receive events, and confirm. Production URLs must use `https://`. Copy the signing secret shown once after registration and store it as an environment secret. You'll use it to [verify wehbhooks](/webhooks/verifying-webhooks). Formable does not show the secret again. Each organization has **one** webhook URL. For e-signing, handle `document_viewed`, `document_signed`, and `document_completed`. Wait for `document_completed` before downloading the signed PDF. The same endpoint also receives redlining events if you use that workflow — inspect `event.event_type` to decide what to act on. ## Manage your endpoint After registration, use the actions menu next to your webhook URL in Settings: | Action | What it does | | ----------------- | --------------------------------------------------------------------------------------------------------- | | **Edit URL** | Changes the callback URL. Does **not** rotate the signing secret. | | **Rotate secret** | Issues a new signing secret and invalidates the old one immediately. Copy the new secret when it's shown. | | **Delete** | Removes the endpoint so you can register a new one. | After you rotate the secret, update your environment before Formable delivers the next event, or signature verification will fail. ## Respond quickly Return a `200` status code as soon as you've received the event. If you have heavy work to do (updating records, sending emails, syncing data), enqueue it and process it asynchronously so you can respond within the 10-second timeout. Any response other than `200`, or a response that takes longer than 10 seconds, is treated as a failed delivery. ## Retries Formable automatically retries failed deliveries up to 7 times over 24 hours. Later attempts are scheduled from the first send: | Attempt | When | | ------- | ---------------- | | 1 | Immediately | | 2 | 30 seconds later | | 3 | 2 minutes later | | 4 | 10 minutes later | | 5 | 1 hour later | | 6 | 6 hours later | | 7 | 24 hours later | After 7 failed attempts, Formable stops retrying that event. To stay resilient: * Make your endpoint highly available and fast to respond. * Use the [events endpoints](/api-reference/endpoint/get-signature-request-events) to reconcile any events you may have missed during downtime. ## Idempotency Design your handler to be idempotent. Because a delivery could theoretically arrive more than once, track the `event.event_id` you've already processed and skip duplicates. ```javascript theme={null} if (await alreadyProcessed(payload.event.event_id)) { return res.sendStatus(200); } await handleEvent(payload); await markProcessed(payload.event.event_id); res.sendStatus(200); ``` ## Next steps Confirm each request genuinely came from Formable. Every event type and its payload schema. # Verifying webhooks Source: https://docs.formabledocs.com/webhooks/verifying-webhooks Confirm that a webhook request genuinely came from Formable. Every webhook request includes a signature so you can verify it was sent by Formable and wasn't tampered with in transit. Always verify the signature before acting on a webhook. ## How HMAC validation works Anyone who learns your webhook URL could send a fake `POST` to it. HMAC Signature verification stops that: Formable and your server share a secret that only the two of you know. On every delivery, Formable uses that secret to stamp the request body, and you check the stamp before trusting the event. Think of it like a wax seal on a letter: 1. Formable takes the exact request body and your signing secret, and runs them through a one-way function (HMAC-SHA256). The result is a short fingerprint. 2. That fingerprint is sent in the `Content-Sha256` header. 3. Your server repeats the same calculation with the same secret and the raw body you received. 4. If your fingerprint matches the header, the body came from Formable and wasn't altered. If it doesn't match, reject the request. An attacker without the secret can't forge a matching fingerprint. Changing even one character of the body produces a different fingerprint, so tampering is caught too. Use the signing secret Formable showed once when you [registered the webhook](/developer-account#register-a-webhook). Store it as an environment secret and use that same value to verify every request. Your signing secret is provided as a **base64-encoded string**. Decode it to raw bytes and use those bytes as the HMAC key. If you misplaced the secret or don't remember it, Formable cannot show it again. Rotate it from **Settings → Webhooks → Rotate secret**, copy the new secret when it's shown, and update your environment. The previous secret stops working immediately. To verify a request: Capture the request body exactly as received, before any JSON parsing or re-serialization. Re-serializing can change whitespace or key order and break verification. HMAC-SHA256 the raw body using the base64-decoded secret as the key, then base64-encode the digest. Compare your computed value with the `Content-Sha256` header using a constant-time comparison. If they match, the request is authentic. ## Examples ```javascript Node.js (Express) theme={null} import express from "express"; import crypto from "crypto"; const app = express(); // Capture the raw body so the signature is computed over the exact bytes. app.use("/webhooks/formable", express.raw({ type: "application/json" })); app.post("/webhooks/formable", (req, res) => { const received = req.header("Content-Sha256"); const secret = Buffer.from(process.env.FORMABLE_WEBHOOK_SECRET, "base64"); const expected = crypto .createHmac("sha256", secret) .update(req.body) // req.body is a Buffer of the raw bytes .digest("base64"); const valid = received && expected.length === received.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received)); if (!valid) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(req.body.toString("utf8")); // handle event... res.sendStatus(200); }); ``` ```python Python (Flask) theme={null} import base64 import hashlib import hmac import os from flask import Flask, request, abort app = Flask(__name__) @app.post("/webhooks/formable") def formable_webhook(): received = request.headers.get("Content-Sha256", "") secret = base64.b64decode(os.environ["FORMABLE_WEBHOOK_SECRET"]) # request.get_data() returns the exact raw bytes of the body. digest = hmac.new(secret, request.get_data(), hashlib.sha256).digest() expected = base64.b64encode(digest).decode() if not hmac.compare_digest(expected, received): abort(401) event = request.get_json() # handle event... return "", 200 ``` ```php PHP theme={null} $rawBody = file_get_contents('php://input'); $headers = getallheaders(); $received = $headers['Content-Sha256'] ?? ''; $secret = base64_decode(getenv('FORMABLE_WEBHOOK_SECRET'), true); $expected = base64_encode(hash_hmac('sha256', $rawBody, $secret, true)); if (!hash_equals($expected, $received)) { http_response_code(401); exit('Invalid signature'); } $event = json_decode($rawBody, true); // handle event... http_response_code(200); ``` ```java Java theme={null} import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; byte[] rawBody = inputStream.readAllBytes(); // raw bytes, before JSON parsing String received = headers.get("Content-Sha256"); byte[] secret = Base64.getDecoder().decode(System.getenv("FORMABLE_WEBHOOK_SECRET")); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret, "HmacSHA256")); String expected = Base64.getEncoder().encodeToString(mac.doFinal(rawBody)); if (received == null || !MessageDigest.isEqual( expected.getBytes(StandardCharsets.UTF_8), received.getBytes(StandardCharsets.UTF_8))) { // return 401 Invalid signature } // handle event... ``` ```go Go theme={null} import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "io" "net/http" "os" ) secret, err := base64.StdEncoding.DecodeString(os.Getenv("FORMABLE_WEBHOOK_SECRET")) if err != nil { http.Error(w, "Invalid secret", http.StatusInternalServerError) return } rawBody, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Invalid body", http.StatusBadRequest) return } mac := hmac.New(sha256.New, secret) mac.Write(rawBody) expected := base64.StdEncoding.EncodeToString(mac.Sum(nil)) received := r.Header.Get("Content-Sha256") if !hmac.Equal([]byte(expected), []byte(received)) { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } // handle event... w.WriteHeader(http.StatusOK) ``` ```ruby Ruby theme={null} require "base64" require "json" require "openssl" raw_body = request.body.read received = request.get_header("HTTP_CONTENT_SHA256").to_s secret = Base64.decode64(ENV.fetch("FORMABLE_WEBHOOK_SECRET")) expected = Base64.strict_encode64( OpenSSL::HMAC.digest("SHA256", secret, raw_body) ) unless OpenSSL.fixed_length_secure_compare(expected, received) halt 401, "Invalid signature" end event = JSON.parse(raw_body) # handle event... status 200 ``` ```csharp C# theme={null} using System.Security.Cryptography; using System.Text; var rawBody = await new StreamContent(Request.Body).ReadAsByteArrayAsync(); var received = Request.Headers["Content-Sha256"].ToString(); var secret = Convert.FromBase64String( Environment.GetEnvironmentVariable("FORMABLE_WEBHOOK_SECRET")!); using var hmac = new HMACSHA256(secret); var expected = Convert.ToBase64String(hmac.ComputeHash(rawBody)); if (!CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(received))) { return Results.Unauthorized(); } // handle event... return Results.Ok(); ``` ## Common pitfalls Frameworks that auto-parse JSON discard the original bytes. Re-serializing the parsed object almost always produces a slightly different string (whitespace, key order), so the HMAC won't match. Always sign over the raw request body. The secret is a base64 string. Use its decoded bytes as the HMAC key — don't pass the base64 string directly. Compare signatures with a constant-time function (`crypto.timingSafeEqual`, `hmac.compare_digest`, `hash_equals`, `MessageDigest.isEqual`, `hmac.Equal`, `OpenSSL.fixed_length_secure_compare`, `CryptographicOperations.FixedTimeEquals`) to avoid timing attacks. Keep your signing secret confidential. If it is exposed, misplaced, or forgotten, rotate it from **Settings → Webhooks → Rotate secret** and update your verifier right away.