Skip to main content

Embedded components

Embedded JavaScript SDK

Client reference for inkfree-embed.js: mount widgets in iframes, handle real-time postMessage events, apply brand themes, and manage runtime error states.

Last updated:

On this page

Loading the SDK

The Inkfree Embedded JavaScript SDK (inkfree-embed.js) is a standalone, zero-dependency library that mounts widgets in an iframe, automatically resizes containers, handles secure cross-window postMessage communications, and applies brand styling.

Script include
<!-- Load the SDK from the Inkfree host -->
<script src="https://sign.inkfree.app/embed/inkfree-embed.js"></script>

Origin discovery

Always load the SDK directly from your Inkfree host. The script inspects its own URL to resolve the target Inkfree origin, preventing accidental cross-origin misconfigurations.

Mounting widgets

Use InkfreeEmbed.mount(options) to initialize either the sender widget or the signing widget:

Mount syntax and lifecycle
var widget = InkfreeEmbed.mount({
  container: "#widget-container",       // DOM element or CSS selector string
  type: "sign",                        // "sign" for signing or "create" for sender widget
  token: sessionToken,                 // 300s one-time token minted by your server
  theme: {                             // Optional brand styling
    primaryColor: "#0a5aff",
    buttonTextColor: "#ffffff"
  },
  minHeight: "540px",                  // Initial frame minimum height
  title: "Document Signing"            // Accessible iframe title
});

// Subscribe to real-time events:
widget.on("sign_completed", function (event) {
  console.log("Sign completed for envelope:", event.requestId);
});

// Remove iframe and cleanup listeners when navigating away:
// widget.destroy();

mount() options reference

OptionRequiredDescription
containerYesA CSS selector string (e.g. "#box") or a DOM element. Throws an error if not found.
typeYes"sign" for the signing widget or "create" for the sender widget.
tokenYesThe single-use token your server minted with POST /api/v1/inkfree/embed/sessions. Use a creation token with type: "create" and a signing token (for a signer or an approver) with type: "sign".
themeNoCustom colors object with primaryColor and buttonTextColor in #rrggbb format.
minHeightNoInitial frame minimum height (default "480px"). Recommended "820px" for the sender widget.
titleNoAccessible name for the iframe title attribute.

What mount() returns and throws

  • mount() returns the widget object. It has on(event, callback), off(event, callback) (both return the widget, so calls can be chained), destroy() and the iframe element.
  • It throws an Error synchronously if type is not "sign" or "create", if token is missing, if the container is not found, or if the script was not loaded from the Inkfree app host.
  • A token problem (used, expired, invalid) does not throw. It appears inside the widget as an error screen. See the errors reference below.

Iframe sandboxing & lifecycle

  • Automatic sandboxing: The SDK mounts the iframe with sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads", an empty allow list, and referrerpolicy="strict-origin", so only your page's origin (never its path) is sent as the referrer.
  • Always use the SDK (not hand-written iframes): The SDK manages origin verification, cryptographic fragment passing, cross-window messaging, dynamic auto-resizing, and brand styling. Hand-written iframe URLs are internal and subject to change.
  • Resizing & ceilings: The SDK automatically resizes the iframe to fit document content. The sender widget sizes itself to frame height; set a sensible minHeight (recommended "820px") and, if you want a ceiling, constrain the iframe with CSS (e.g. iframe { max-height: 900px; }).
  • Instance isolation: Multiple widgets can coexist on a single parent page. The SDK filters postMessage events by origin and window reference so each instance only receives its own events.
  • Cleanup with destroy(): Calling widget.destroy() removes the iframe from the DOM and detaches message event listeners. Always call this before re-mounting a new widget in the same container.

Custom themes and branding

Pass a theme object to mount() to match your company's UI:

Theme object
theme: {
  primaryColor: "#0a7d5c",     // Hex color for buttons, active steppers, and focus states
  buttonTextColor: "#ffffff"   // Text color for filled primary buttons
}

What works now

  • The colors apply across the widget: action buttons (including Next, Back, Send, Finish, Adopt & sign, Complete Signing), hover and active states, focus rings, selected options, and signer-side accents.
  • buttonTextColor specifies the foreground text color on filled buttons. Choose a text color with sufficient contrast against your primaryColor.

Order of precedence

  1. Widget theme: Colors passed to mount({ theme }).
  2. Account branding: Colors configured in your Inkfree dashboard under Settings > Branding.
  3. Inkfree default: Fallback default color palette.

Hex code validation

Colors must follow 6-digit hex format (e.g. #0a7d5c). Invalid formats are ignored and reported in the browser console (InkfreeEmbed: theme.primaryColor must look like #0a7d5c) and fall back safely.

Limits

  • Only two colors are supported (primaryColor and buttonTextColor). Logos, custom fonts, injected CSS, border radius adjustments, and dark mode are not supported. Parent pages cannot style the inner DOM of the iframe.
  • Certain parts of the sender widget keep the default Inkfree colors (such as portions of the step indicator bar and step layouts). Validate your color combinations on both sender and signer screens prior to launch.
  • Because colors reach the iframe shortly after initialization, a brief flash of default colors is possible on first paint.

Events reference

Subscribe to events using widget.on(event, callback) and unsubscribe with widget.off(event, callback):

EventWidgetWhendata payload
readybothThe widget opened its session.{}
signer_viewedsignThe document is shown to the signer.{ signerRole }
sign_startedsignThe signer filled the first field or adopted a signature.{ signerRole }
sign_completedsignInkfree accepted this signer's signature (other signers may still be pending).{ signerRole, completedAt }
sign_declinedsignThe signer declined. The reason stays in Inkfree.{ signerRole }
approval_viewedsignThe document is shown to an approver (the envelope is InReview).{ signerRole: "Approver" }
approval_completedsignInkfree accepted the approver's approval (other approvers may still be pending).{ signerRole: "Approver" }
approval_rejectedsignThe approver rejected the envelope; it is now Rejected.{ signerRole: "Approver" }
send_completedcreateThe user pressed Send and the server accepted the draft. status is the envelope's status: Pending (signers invited now), InReview (approvers must approve first, inside your app) or Scheduled (sent later, at the envelope's scheduleTime). Mint signer tokens once the envelope is Pending (approver tokens while it is InReview); see After the draft is sent.{ status }
errorbothAn error occurred during operation (see error codes below).{ code, recoverable }

Your callback receives one object: { type, requestId, ts, data }. type is the event name, requestId is the draft or envelope id, ts is the time in milliseconds, and data is the payload from the table above. An error event carries data.code and data.recoverable.

Event data privacy & integrity

Events never contain personal details such as names, emails, raw tokens, or document content. After a terminal event (sign_completed, sign_declined, approval_completed, approval_rejected or send_completed), the widget stays on a static confirmation panel; your parent page decides when to unmount it. Client events are hints, not legal proof: always confirm state on your server via webhook or API.

Errors reference

Widget errors (the 'error' event)

CodeMeaningWhat to do
TOKEN_INVALID
TOKEN_EXPIRED
TOKEN_USED
The token cannot be redeemed. No error event is sent for these (nor for ORIGIN_MISMATCH): the widget learns your page's origin only from a redeemed token, so it has nowhere to post to. It shows the message inline. If you need to detect it, watch for the missing ready event.Mint a new token from your backend server.
ORIGIN_MISMATCHThe page showing the widget is not the registered origin the token was minted for, or that origin was removed. The token is not consumed. No event is sent; the widget shows the error message inline.Mount the widget only from the origin you minted the token for, and check the origin is still registered.
SESSION_EXPIREDThe 60-minute session ended, or the API key was revoked.Mint a new token and re-mount the widget.
NOT_YOUR_TURNAn earlier signer in the signing order has not signed yet.Wait for the signer.signed webhook before opening the signing widget.
REQUEST_NOT_FOUND
REQUEST_EXPIRED
REQUEST_VOIDED
REQUEST_DECLINED
REQUEST_COMPLETED
The request state does not permit this action (e.g. envelope is already completed or voided).Query envelope status from your backend and update the parent UI.
SIGNER_LOCKEDThe widget was refused with HTTP 429 (too many requests for the API key that minted the session).Wait a minute and try again. If it keeps happening, see Rate limits.
VALIDATION_FAILEDThe user has not completed a step, or the send was refused (for example a signer has no placed field). Recoverable.The user corrects the issue inline in the widget; the validation message appears next to the field.
INSUFFICIENT_CREDITS
PLAN_LIMIT
INSUFFICIENT_CREDITS: the account has no credits left for this action. PLAN_LIMIT: the action is not included in the account's plan, or an admin has turned it off.Check the account's plan and credits in Inkfree.
VIEWPORT_TOO_SMALLThe sender widget container is narrower than 320px. Recoverable.Make the iframe container wider (minimum 320px).
NETWORK
INTERNAL
Network connection or server problem.Retry; contact support if the issue persists.

If the session ends while a user is working, the widget shows "Your session has ended.", sends an error event with SESSION_EXPIRED, and your page should mint a new token. The widget never redirects the user to an Inkfree login page. The text inside the widget is fixed for each code; the detailed server message is for your server logs only.

API errors (your server's calls)

Most errors from the mint and draft calls use the standard error body described in Errors: { "message": "CODE: explanation", "code": 400, "correlationId": "..." }. The message starts with the code name. Quote the correlationId when you contact support. A missing or invalid API key (HTTP 401) is rejected before the request reaches the embed code, so that body has the form { "timestamp", "status": 401, "error", "message": "invalid api key", "path" }. Always branch on the HTTP status and the code at the start of message.

HTTPCode in messageCause & Solution
400VALIDATION_FAILEDA draft setting is invalid: an invalid signer email, duplicate signer ids, a subject over 100 characters, a message over 1000 characters, expirationDays below 1, or a field assigned to a signer that does not exist.
400(no code)A mint request is missing requestId or signerEmail, or scope is not signing or creation. The message is plain text, for example scope must be 'signing' or 'creation'.
400SIGNER_NOT_EMBEDDEDThe signer's delivery mode is not EMBEDDED (an EMAIL signer signs through the link Inkfree sent them, found in the signer's url on the envelope) or the signer is a CC recipient. For an approver: the approver's delivery mode is not EMBEDDED.
400NOT_YOUR_TURNThe signer is not up yet: an earlier level of the signing order has not finished, or the signer's own status is not Pending (for example a signing delay has not elapsed), or, for an approver, an earlier approver has not approved or a group peer already did. Try again after the signer.signed webhook, or poll the envelope.
400REQUEST_COMPLETED
REQUEST_VOIDED
REQUEST_EXPIRED
REQUEST_DECLINED
REQUEST_NOT_READY
The request is not in the required state: a draft for creation, Pending for a signer and InReview for an approver. For a signer, REQUEST_NOT_READY means the envelope is still InReview (waiting for approvers; an approver's own token is minted in this state), Scheduled (its send time has not come) or still a draft; poll GET /api/v1/inkfree/envelopes/{id} until it is Pending. A signer who has already signed (or an approver who already approved) gets REQUEST_COMPLETED; a rejected envelope gives REQUEST_DECLINED.
401invalid api keyMissing, invalid, revoked or expired API key.
403ORIGIN_NOT_REGISTEREDparentOrigin is not registered under Settings > API Keys > Embedded signing origins. A 403 is also returned when the account is scheduled for deletion.
406(no code)Draft or envelope creation breaks an embedded signer rule. The message starts Exception submitting embedded sign request. Reason :: and lists each problem (invalid email, CC or reviewer, delivery by SMS, or an embedded approver without a valid email). An unknown deliveryMode also returns 406.
404REQUEST_NOT_FOUND
SIGNER_NOT_FOUND
No such request exists, or the specified email is not a signer on it.

When the widget redeems a token inside the iframe, a failure comes back as HTTP 401 with TOKEN_INVALID, TOKEN_EXPIRED or TOKEN_USED. You do not make that call yourself; the widget does, and shows the matching message. Mint a new token to retry.

Testing your integration

1. Test environment setup

  1. Create a test API key in your Inkfree dashboard and register your local origin (e.g. http://localhost:5173).
  2. Implement the backend token minting route and frontend mounting script.
  3. Revoke test keys upon completing security testing.

2. Comprehensive verification checklist

AreaScenario to testExpected result
Sender widgetCreate an empty draft, open the sender widget, go through all four steps, then click Send.Steps start empty. send_completed event fires. Envelope moves to Pending status.
ValidationClick Next with no document uploaded, a blank signer name, a signer without fields, or an empty subject.Inline validation error is shown and the step does not advance.
Back and NextGo back and forward between the four steps.Previously entered documents, signers, fields, and message settings are preserved.
SigningMint a signing token for each embedded signer and complete the document.sign_completed fires; the envelope becomes signed after the final signer completes.
Signing orderAttempt to mint a signing token for Signer 2 before Signer 1 has finished.Refused with NOT_YOUR_TURN error.
ApproversAdd an approver in the sender widget and send. Mint a signing token with the approver's email, approve in the widget; repeat with a reject on another envelope.Envelope is InReview after send; approval_completed fires and the envelope becomes Pending, or approval_rejected and Rejected. The approver gets no email.
DeclineDecline the agreement as an active signer.sign_declined event fires; envelope status changes to Declined.
TokensReuse a token, wait over 300 seconds, alter a token character, or mount a signing token in a create widget.Each attempt is strictly rejected.
OriginMint a token using an unregistered parentOrigin.Rejected with ORIGIN_NOT_REGISTERED (HTTP 403).
Embedding pageMount a widget with a token minted for another registered origin, or from a page on an unregistered origin.The widget shows an error and the token is not consumed (ORIGIN_MISMATCH).
API keyCall the mint endpoint with an invalid key, then revoke a key while a sender widget is open and click anything in it.HTTP 401 for the invalid key. For the open sender widget, the next request fails and the widget shows "Your session has ended."
ThemesTest various hex color pairs, a light primary color with dark text, no colors, and an invalid hex string.Buttons follow custom colors; omissions fall back to account branding or Inkfree defaults; invalid hex logs a console warning.
ConfirmationAfter each flow, query the envelope status directly from your server.Server-reported status accurately matches client event sequence.
Mixed signersCreate an envelope with one EMBEDDED signer and one EMAIL signer.The email signer receives the normal Inkfree email. The embedded signer receives no email and signs only through a token from your server.

Embedding page check

The widget redeems a token only when the page that embeds it is the registered origin the token was minted for. Any other page gets ORIGIN_MISMATCH and the token is not consumed. This applies in every environment, including local development.

Feature availability

Available features

  • API-key authentication, origin whitelisting, one-time 300s tokens, and 60-minute sessions.
  • Embedded signing: signing order sequence, field navigator, adopt or draw signature, decline, and postMessage events.
  • Embedded approvers: approvers approve or reject inside your app in the signing widget, with approval order and approver groups. Inkfree emails them nothing.
  • Embedded sender widget: the same four steps as Inkfree's own wizard: upload documents, signers and CC recipients with a signing order and signing delays, approvers and approver groups (who approve inside your app), drag-and-drop field placement with the account's custom fields and AI auto-place, the AI assistant and AI insights, email subject and message, expiry, signer permissions (download, completion notification, attach signed copies), scheduled send, and a branded email preview. Delegation and reminder emails are not offered, because they would take a signer out of your app. Each option needs the matching feature on the account's plan.
  • Security for every signer, including those who sign in your app: an access code, a one-time code by email or by text, and identity verification where it is set up. The signer is asked for it inside the iframe.
  • Optional document uploads and signer pre-population on draft creation; pre-placed field templates; idempotent requests.
  • Signers added in the sender widget sign in your app (EMBEDDED); partner-specified signers retain their mode.
  • JavaScript client SDK with event subscriptions, container auto-resizing, and two-color theme support with account branding fallback.
  • Server webhooks and envelope REST API for verification and signed PDF retrieval.

Not available

CapabilityStatus & Notes
Choosing which steps the sender widget shows (for example the field editor only)Not available. The sender widget always shows all four steps.
Custom logos, web fonts, injected CSS, dark modeNot built. Sandboxed iframes prevent custom CSS injection.
Theme persistence per API key; dynamic theme updatesNot built. Themes are supplied per mount() call.
Full theming of every part of the sender widgetPartially supported; stepper track and certain step layouts retain default colors.
In-person signing, saved templates, cloud drive imports, creating or editing custom fields, the eSign-mode choiceOmitted from the embedded sender widget. In-person signing is not offered to partner apps; the others need an Inkfree account login.
Resuming sender sessions after browser reloadNot supported. Mint a new token to reopen the sender widget on the existing draft.
Bulk send with embedded signersNot supported.

Known considerations

  • Signer page colors: Verify your color contrast across every step of the signing workflow before deploying to production.
  • Responsive layout: Both widgets switch to a phone layout when the iframe is narrow, and switch back when it is widened. The sender widget needs a frame at least 320px wide; below that it shows the VIEWPORT_TOO_SMALL message.

Complete code samples

Frontend HTML + JavaScript

Complete frontend integration
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>In-App Signing</title>
  <script src="https://sign.inkfree.app/embed/inkfree-embed.js"></script>
</head>
<body>
  <div id="signing-box" style="width: 100%; height: 800px;"></div>

  <script>
    // 1. Fetch one-time token from your server
    fetch("/api/get-signing-token?envelopeId=12345")
      .then(res => res.json())
      .then(data => {
        // 2. Mount the Inkfree signing widget
        var widget = InkfreeEmbed.mount({
          container: "#signing-box",
          type: "sign",
          token: data.token,
          theme: {
            primaryColor: "#0a7d5c",
            buttonTextColor: "#ffffff"
          }
        });

        // 3. Handle events
        widget.on("sign_completed", function (event) {
          alert("Thank you for signing!");
          window.location.href = "/dashboard";
        });

        widget.on("error", function (err) {
          console.error("Widget error:", err.data.code);
        });
      });
  </script>
</body>
</html>

Backend server: Mint signing token

curl mint signing token
curl -X POST "https://services.softsages.com/core/api/v1/inkfree/embed/sessions" \
  -H "X-API-Key: sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "signing",
    "requestId": "envelope-id-here",
    "signerEmail": "signer@example.com",
    "parentOrigin": "https://app.example.com"
  }'

Backend server: Create empty draft for sender widget

curl create draft
curl -X POST "https://services.softsages.com/core/api/v1/inkfree/embed/drafts" \
  -H "X-API-Key: sk_live_YOUR_API_KEY" \
  -F 'request={"emailSubject":"Vendor NDA","emailMessage":"Please sign the agreement.","expirationDays":30,"signers":[]}'