Embedded components
Embedded components overview
Put Inkfree sending and signing inside your own web app with an iframe. Your users never leave your site, and they never need an Inkfree login.
Last updated:
On this page
How it works
Your server talks to Inkfree with an API key. Your web page loads one script, inkfree-embed.js, which opens an Inkfree widget in an iframe. The two are connected by a short-lived, one-time token that your server asks for each time a user opens the widget.
- Your server creates a draft (for the sender widget) or an envelope with an embedded signer or approver (for the signing widget).
- Your server asks Inkfree for a one-time token with its API key. The token works once and expires after 300 seconds.
- Your page mounts the widget with that token. The SDK opens the iframe and puts the token in the URL fragment.
- The iframe exchanges the token once for a session that lasts 60 minutes.
- Your user works inside the iframe. The widget sends events (
ready,sign_completedand others) to your page. - Your server confirms the result with a webhook or the REST API. Browser events are hints, not proof.
Inkfree provides two embedded widgets:
| Widget | type | Who uses it | What it shows |
|---|---|---|---|
| Sender widget | "create" | Your user who prepares & sends a document | Four steps: upload document, signers, drag-and-drop fields, email settings, then Send. |
| Signing widget | "sign" | The recipient who signs, or an approver who approves before the signers | The document viewer, interactive fields, adopt/draw signature, finish, or decline. For an approver: the document with Approve and Reject. |
You can use either widget on its own, or both together: let a user prepare and send a draft in the sender widget, then open the signing widget inside your app for each signer.
Before you start
1. API key. Create an API key in Inkfree under Settings > API Keys. Keep this key on your backend server only. Embedded Components has no separate plan switch, but sending from the sender widget needs the same send entitlement as sending from the Inkfree app. If your plan does not include it, the widget reports PLAN_LIMIT.
2. Register your web origin. An account admin adds every web origin that will show the widget (for example https://app.example.com) under Settings > API Keys > Embedded signing origins. Only account admins can add or remove origins. An account can register up to 20.
Origin registration rules
An origin is a scheme, a host and an optional port, for example https://app.example.com or https://app.example.com:8443. Inkfree lowercases it, drops a default port (:443) and ignores a single trailing slash. A path, query string, fragment, user name or wildcard such as *.example.com is rejected. Plain http is accepted only for localhost and 127.0.0.1, for local testing.
3. Load the SDK from the Inkfree app host. The Inkfree app host for this documentation is https://sign.inkfree.app. The SDK works out the Inkfree address from the address it was loaded from, so always load it from that host:
<script src="https://sign.inkfree.app/embed/inkfree-embed.js"></script>API and app hosts
Use https://services.softsages.com/core as the API base URL for your server calls and https://sign.inkfree.app as the app host for the SDK. Create the API key and register your origin in the same Inkfree account you will use with these hosts.
4. Allow the frame on your own page. If your site sends a Content-Security-Policy header, allow the Inkfree app host for scripts and frames: script-src https://sign.inkfree.app and frame-src https://sign.inkfree.app. Your page must be served over https (or be http://localhost while you develop), because that is the only kind of origin you can register.
Authentication and trust
| Credential | Held by | Used for | Lifetime |
|---|---|---|---|
API key (X-API-Key) | Your server only | Creating drafts and envelopes, minting tokens, querying results. | Persistent until explicitly revoked. |
| Embed token | Your server, then passed to browser | Redeeming one widget session, once. | 300 seconds (5 minutes), single use. |
| Embed session | The widget, in memory only | Authorizing actions inside the widget editor or signer. | 60 minutes. |
What a token is bound to
When you mint a token, Inkfree ties it to your account, the API key that minted it, one request (a draft or an envelope), the scope (creation for the sender widget or signing for a signer), the signer's or approver's email (signing only) and the registered parentOrigin. A token cannot open any other draft, envelope or signer. Inkfree stores only a one-way hash of the token, so the token itself is shown to you once, in the mint response.
Your application verifies identity
Inkfree does not ask embedded users to log in. It trusts your server to sign your own users in and to mint a token only for the right person. Your sign-in is the first identity check. The sender can still add a second one for a signer (an access code, a one-time code by email or by text, or identity verification), and the signer is asked for it inside the iframe.
The envelope ID is not a credential
Knowing an envelope or draft id grants no access. Reading an envelope or minting a token needs your secret X-API-Key. Signing needs the session that Inkfree issues to the iframe, and only after a valid one-time token is exchanged. Treat ids as ordinary identifiers.
Tokens and sessions
Token lifetime: A token lives 300 seconds, counted from the moment you mint it, and works once. Mint it when the user opens your page, not in advance. Never cache or reuse a token. A token that is expired, already used or invalid is refused with TOKEN_EXPIRED, TOKEN_USED or TOKEN_INVALID.
Session duration: The 60 minutes start when the widget redeems the token. A signing session lasts 60 minutes. A sending session also lasts 60 minutes and ends after one successful send. Activity does not extend a session.
Browser reload behavior:
- Signing: Reloading the page within the 60 minutes continues the same signing session. After that, your application must mint a new token.
- Sending: A reload ends the session, because the token was already used. Documents already uploaded stay on the draft. Signers, placed fields and email settings are saved only when the user clicks Send, so those are lost. Mint a new token to continue the same draft. Sender sessions do not resume.
API key revocation: Revoking an API key stops it from minting new tokens or creating drafts. A sending session started with that key ends on its next request. A signing session that is already open can keep working until its 60 minutes run out.
Backend minting: Always mint tokens from an authenticated endpoint on your backend that first confirms your own user is logged in and authorized to access this specific request.
Origins, framing and security
Origins and token binding: A token is bound to one registered origin, and you can only mint a token for an origin your account has registered (otherwise ORIGIN_NOT_REGISTERED, HTTP 403). The widget sends its events only to that origin, and the SDK checks the origin and the window of every message it receives.
Framing: The widget reads the page that embeds it from the browser (not from the URL) and sends it with the token. Inkfree redeems the token only if that page is the registered origin the token was minted for and the origin is still registered. Otherwise it is refused with ORIGIN_MISMATCH and the token is not consumed. If the widget is nested inside pages from several origins, or the browser reveals no embedding page, it is refused too. Removing an origin in Settings stops its unredeemed tokens at once. The same check applies in local development.
CORS: You do not configure CORS for Embedded Components. Your server calls the Inkfree API (no browser is involved), and the widget calls Inkfree from the Inkfree app's own origin. The only setup on your side is registering your page's origin.
Token in the URL fragment: The SDK passes the token in the URL fragment (#token=...). Browsers never send the fragment to a server, so it does not appear in request logs. The widget removes it from the address bar as soon as it loads.
Sandbox and privacy: 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.
No Inkfree login: The widget never asks for an Inkfree login and never redirects to one. It keeps its state in memory only and does not read or write the browser's stored Inkfree login.
Scoped sessions: A sender session works only on the sender widget endpoints and only for its own draft. A signing session works only for its own signer on its own envelope. The server re-validates signers, delivery modes, fields and email settings every time the draft is saved and again when it is sent. It never relies on the browser's checks alone.
Audit trail: Token minting, token redemption, the first moment a signer starts signing, and every refused token for a known request are recorded on the request's audit trail, next to the usual opened, signed and declined entries.
Rate limits: Sender widget calls count toward the rate limit of the API key that minted the session. See Rate limits.
Your side: Keep your API key on your server. Check that your own user is signed in and allowed to open the request before you mint a token. Never put a token in a page URL query string, an email or a log. Embedded signers cannot delegate.
Third-party scripts: Analytics, tag managers, session recording, Google sign-in and reCAPTCHA are not loaded inside the widget. Stripe.js is part of the Inkfree app and can still load in the frame, but the widgets do not use it. The envelope API does not return a signing or approval link for embedded signers and approvers.
End-to-end example
This example shows the whole sender flow with Node.js 18 or later and Express: your server creates a draft and mints a token for your signed-in user, and your page mounts the widget. For the signing widget, skip the draft, send an envelope with an EMBEDDED signer, and mint a token with scope: "signing" and signerEmail. See Embedded signing.
// server.js -- your backend. The API key lives only here (set INKFREE_API_KEY in the environment).
const express = require("express");
const app = express();
app.use(express.json());
const INKFREE_API = "https://services.softsages.com/core";
const API_KEY = process.env.INKFREE_API_KEY;
const PARENT_ORIGIN = "https://app.example.com"; // the origin you registered in Inkfree
async function inkfree(path, init) {
const res = await fetch(INKFREE_API + path, {
...init,
headers: { "X-API-Key": API_KEY, ...(init.headers || {}) },
});
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.message), { status: res.status });
return body;
}
// Your own sign-in check. Replace with your real session or JWT middleware.
function requireUser(req, res, next) {
if (!req.headers.authorization) return res.status(401).json({ message: "sign in first" });
next();
}
// Called by your page when the user opens "Send a document".
app.post("/api/inkfree/sender-token", requireUser, async (req, res) => {
try {
// 1. Create the draft. The draft id is in the "message" field of the response.
const form = new FormData();
form.append("request", JSON.stringify({
emailSubject: "Please sign this agreement",
expirationDays: 30,
signers: [],
}));
const draft = await inkfree("/api/v1/inkfree/embed/drafts", { method: "POST", body: form });
// 2. Mint the one-time token (valid for 300 seconds, works once).
const session = await inkfree("/api/v1/inkfree/embed/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ scope: "creation", requestId: draft.message, parentOrigin: PARENT_ORIGIN }),
});
res.json({ token: session.token });
} catch (err) {
res.status(err.status || 500).json({ message: err.message });
}
});
app.listen(3000);<div id="sender-widget" style="width: 100%;"></div>
<script src="https://sign.inkfree.app/embed/inkfree-embed.js"></script>
<script>
fetch("/api/inkfree/sender-token", { method: "POST", headers: { Authorization: "Bearer " + yourUserToken } })
.then(function (res) { return res.json(); })
.then(function (data) {
var widget = InkfreeEmbed.mount({
container: "#sender-widget",
type: "create",
token: data.token,
minHeight: "820px"
});
widget.on("send_completed", function (event) {
// A hint only: confirm on your server with the envelope API or a webhook.
// event.data.status is Pending (signers invited), InReview (approvers first)
// or Scheduled (sent later) -- see "After the draft is sent" in the sender guide.
console.log("Sent:", event.requestId, event.data.status);
});
widget.on("error", function (event) {
console.warn("Widget error:", event.data.code);
});
});
</script>To receive results on your server, register a webhook as described in Webhooks and subscribe to signer.signed and envelope.completed.
Explore embedded topics
Follow the guides below to implement each component into your application: