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.
<!-- 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:
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
| Option | Required | Description |
|---|---|---|
container | Yes | A CSS selector string (e.g. "#box") or a DOM element. Throws an error if not found. |
type | Yes | "sign" for the signing widget or "create" for the sender widget. |
token | Yes | The 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". |
theme | No | Custom colors object with primaryColor and buttonTextColor in #rrggbb format. |
minHeight | No | Initial frame minimum height (default "480px"). Recommended "820px" for the sender widget. |
title | No | Accessible name for the iframe title attribute. |
What mount() returns and throws
mount()returns the widget object. It hason(event, callback),off(event, callback)(both return the widget, so calls can be chained),destroy()and theiframeelement.- It throws an
Errorsynchronously iftypeis not"sign"or"create", iftokenis 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 emptyallowlist, andreferrerpolicy="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: {
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.
buttonTextColorspecifies the foreground text color on filled buttons. Choose a text color with sufficient contrast against yourprimaryColor.
Order of precedence
- Widget theme: Colors passed to
mount({ theme }). - Account branding: Colors configured in your Inkfree dashboard under Settings > Branding.
- 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 (
primaryColorandbuttonTextColor). 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):
| Event | Widget | When | data payload |
|---|---|---|---|
ready | both | The widget opened its session. | {} |
signer_viewed | sign | The document is shown to the signer. | { signerRole } |
sign_started | sign | The signer filled the first field or adopted a signature. | { signerRole } |
sign_completed | sign | Inkfree accepted this signer's signature (other signers may still be pending). | { signerRole, completedAt } |
sign_declined | sign | The signer declined. The reason stays in Inkfree. | { signerRole } |
approval_viewed | sign | The document is shown to an approver (the envelope is InReview). | { signerRole: "Approver" } |
approval_completed | sign | Inkfree accepted the approver's approval (other approvers may still be pending). | { signerRole: "Approver" } |
approval_rejected | sign | The approver rejected the envelope; it is now Rejected. | { signerRole: "Approver" } |
send_completed | create | The 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 } |
error | both | An 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)
| Code | Meaning | What to do |
|---|---|---|
TOKEN_INVALIDTOKEN_EXPIREDTOKEN_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_MISMATCH | The 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_EXPIRED | The 60-minute session ended, or the API key was revoked. | Mint a new token and re-mount the widget. |
NOT_YOUR_TURN | An earlier signer in the signing order has not signed yet. | Wait for the signer.signed webhook before opening the signing widget. |
REQUEST_NOT_FOUNDREQUEST_EXPIREDREQUEST_VOIDEDREQUEST_DECLINEDREQUEST_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_LOCKED | The 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_FAILED | The 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_CREDITSPLAN_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_SMALL | The sender widget container is narrower than 320px. Recoverable. | Make the iframe container wider (minimum 320px). |
NETWORKINTERNAL | 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.
| HTTP | Code in message | Cause & Solution |
|---|---|---|
| 400 | VALIDATION_FAILED | A 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'. |
| 400 | SIGNER_NOT_EMBEDDED | The 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. |
| 400 | NOT_YOUR_TURN | The 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. |
| 400 | REQUEST_COMPLETEDREQUEST_VOIDEDREQUEST_EXPIREDREQUEST_DECLINEDREQUEST_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. |
| 401 | invalid api key | Missing, invalid, revoked or expired API key. |
| 403 | ORIGIN_NOT_REGISTERED | parentOrigin 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. |
| 404 | REQUEST_NOT_FOUNDSIGNER_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
- Create a test API key in your Inkfree dashboard and register your local origin (e.g.
http://localhost:5173). - Implement the backend token minting route and frontend mounting script.
- Revoke test keys upon completing security testing.
2. Comprehensive verification checklist
| Area | Scenario to test | Expected result |
|---|---|---|
| Sender widget | Create 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. |
| Validation | Click 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 Next | Go back and forward between the four steps. | Previously entered documents, signers, fields, and message settings are preserved. |
| Signing | Mint a signing token for each embedded signer and complete the document. | sign_completed fires; the envelope becomes signed after the final signer completes. |
| Signing order | Attempt to mint a signing token for Signer 2 before Signer 1 has finished. | Refused with NOT_YOUR_TURN error. |
| Approvers | Add 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. |
| Decline | Decline the agreement as an active signer. | sign_declined event fires; envelope status changes to Declined. |
| Tokens | Reuse a token, wait over 300 seconds, alter a token character, or mount a signing token in a create widget. | Each attempt is strictly rejected. |
| Origin | Mint a token using an unregistered parentOrigin. | Rejected with ORIGIN_NOT_REGISTERED (HTTP 403). |
| Embedding page | Mount 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 key | Call 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." |
| Themes | Test 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. |
| Confirmation | After each flow, query the envelope status directly from your server. | Server-reported status accurately matches client event sequence. |
| Mixed signers | Create 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
| Capability | Status & 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 mode | Not built. Sandboxed iframes prevent custom CSS injection. |
| Theme persistence per API key; dynamic theme updates | Not built. Themes are supplied per mount() call. |
| Full theming of every part of the sender widget | Partially 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 choice | Omitted 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 reload | Not supported. Mint a new token to reopen the sender widget on the existing draft. |
| Bulk send with embedded signers | Not 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_SMALLmessage.
Complete code samples
Frontend HTML + JavaScript
<!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 -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 -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":[]}'