This page describes SDK 1.0 and embed sessions. If you embed with SDK 0.x and the party’s signing token, see Migrate from embed v1.
1.0.0-beta.1 under the beta tag on npm. Pin the exact version: a later beta can break your integration.
@sajn/embed-js: a function for any web page. See JavaScript.@sajn/embed-react: a React component for React 18 and later. See React.@sajn/embed-vue: a Vue component for Vue 3.3 and later. See Vue.
How an embed session works
- The party opens a page in your application.
- The SDK calls your
fetchClientSecretfunction, which asks your server for a client secret. - Your server calls
POST /api/v1/embed-sessionswith your API key and returns theclientSecretfrom the response. - The SDK opens
https://app.sajn.se/embed/v2/{id}in an iframe and hands the client secret to the frame overpostMessage. The frame redeems the secret once. - The party signs in the frame, and the SDK calls your
onSignedcallback. - Your server confirms the signature with the
document.party.signedwebhook.
Before you begin
- Embedded signing is part of the Enterprise plan, and sajn turns it on for each organization. Being on Enterprise doesn’t turn it on. To get started, contact [email protected]. If you build on sajn to send documents to your own customers, you also need a partner license.
- A sandbox has embedded signing turned on when it’s created, so you can build and test there first.
- Store an API key in the
SAJN_API_KEYenvironment variable on your server. The key needs write access to documents. Sessions need an API key: an OAuth app can’t create them. To create a key, go to Inställningar > Utvecklare > API-nycklar (Settings > Developer > API keys) in the sajn app. - Send API version
2026-10in theSajn-Versionheader. The embed session endpoints don’t exist in2026-09, which returns404 ROUTE_NOT_FOUNDfor them.
Add your embed domains
After sajn turns on embedding, the organization’s developer settings get an Inbäddning (Embedding) tab. The domain list belongs to the organization, so every workspace in it shares the list.- In the sajn app, go to Inställningar > Utvecklare > Inbäddning (Settings > Developer > Embedding).
- Enter each domain whose pages show the signing page, such as
app.example.com, and press Enter. - Click Spara ändringar (Save changes).
*.example.commatches every subdomain ofexample.com, at any depth, but notexample.comitself. To allow both, add both.- Only pages served over HTTPS can show the frame.
localhostcan’t be added. To test from your computer, expose your local app on a public HTTPS domain, such as through a tunnel, and add that domain. - An empty list allows no pages, and the API refuses to create sessions.
- sajn checks the list on every request, so removing a domain stops live sessions on it at once.
Embed the signing page
1
Create and send the document on your server
Create the document from a template, fill in the template’s party and values, and send it, all in one request. Set the party’s
delivery to [], so sajn doesn’t also email an invitation:templatePartyIdis the ID of a party in the template’sparties, fromGET /api/v1/templates/TEMPLATE_ID. The document’s copy of that party keeps its signature boxes and settings, and the details you send replace its own. WithouttemplatePartyId, the parties you send replace the template’s parties and their signature boxes are removed.fieldValuesfills in values bykey, in the same shape asvaluesin Fill in values on a document. To list a template’s keys, create a draft and callGET /api/v1/documents/DOCUMENT_ID/field-values.send: truesends the document after sajn creates it. You can create a session only for a sent document.
PENDING. Store its id, and the id of the party whose ref is customer in parties.2
Create an embed session on your server
Add an endpoint to your server that your page calls. The endpoint checks who the user is, creates a session for that user’s party, and returns only the The response looks like this:The SDK calls your endpoint again each time a session ends, so create a new session on every call. Never cache a client secret, log it, or put it in a URL. The session
clientSecret:id isn’t a secret.3
Show the signing page
In your page, give the SDK a container and a Give the container a height. For more information, see Size the frame. For React and Vue, see the framework pages.
fetchClientSecret function that calls your endpoint:4
Confirm the signature on your server
onSigned runs in the browser, so use it to update the screen, not as proof. Confirm the signature with the document.party.signed webhook event. The event means that this party’s signature is recorded. To act on the sealed PDF, wait for document.completed.Session lifetime
A session ends at the first of the following:- The frame didn’t redeem the client secret within 10 minutes of creation.
clientSecretExpiresAtin the response is this limit. - The frame had no activity for an hour. A frame in a hidden tab doesn’t keep its session active.
- 24 hours passed since creation.
expiresAtin the response is this limit. - You revoked the session with
DELETE /api/v1/embed-sessions/{id}, which returns{ "id": "emb_…", "revoked": true }. - The API key that created the session was revoked or rotated.
fetchClientSecret again for a new session. The party keeps their progress: a new session for a party who signed shows the signed state. If sessions end three times within one minute, the SDK stops and raises session_expired.
Create session request
POST /api/v1/embed-sessions takes the following body:
The endpoint returns the following errors, in addition to the common errors:
Features
sajn enforces features on the server, so the browser can’t turn them on. A feature that you leave out keeps its default.Appearance
You can setappearance on the session and in the SDK. The SDK’s values replace the session’s values key by key at the top level, so an SDK colors object replaces the session’s whole colors object. Custom CSS isn’t supported.
Text must stay readable, so sajn checks two color pairs for a contrast ratio of at least 4.5:1:
foreground on background, and primaryForeground on primary. If colorScheme isn’t dark and you set only one color of a pair, sajn checks it against its light default for the other color. A session request that fails the check returns 400 VALIDATION_FAILED. An SDK appearance that fails the check is ignored as a whole, and the frame keeps its current look.
The consent text, the party’s identity, the wording of the sign buttons, and the document itself keep their styles under any appearance.
Size the frame
The sign view is static: the document, and under it a sign section that is always visible. The frame shows no dialogs or overlays, so it works the same at any size.fixed(default): the frame fills its container and scrolls inside. Give the container a height, at least 600px on phones. If the container has no height of its own, the SDK sets the frame tofallbackHeight, which is 600px by default.inline: the frame grows to the height of its content, and your page scrolls. Don’t give the container a fixed height.
fixed, because the read check needs a frame that scrolls.
Signing methods in the frame
Swedish BankID runs inside the frame, by QR code or on the same device. Click-to-sign and drawn signatures also run in the frame. The providers of the following eIDs refuse to run inside a frame: MitID, Norwegian BankID, FTN, iDIN, and qualified electronic signatures (QES). For these, the frame opens the provider in a new window when the party clicks the sign button. The frame updates itself when the signature lands, even if the window doesn’t report back. If the browser blocks the window, the button turns into a link to the same page. If the document has an access check (openVerification), the party passes it inside the frame before the document loads, the same as on the hosted page. Your application has usually signed the user in already, so consider leaving openVerification set to null for embedded documents. A party’s own signVerification always applies before they sign.
Events
Events are hints for your page. They carrydocumentId, partyId, and externalId, never a credential. Confirm outcomes on your server with webhooks.
EmbedState has the following fields:
Error codes
onError receives one of the following codes:
For
session_not_found, session_expired, and session_revoked from the frame, onError runs first, then onSessionExpired, and then the SDK starts a new session. Don’t show a final error for these codes. For invalid_appearance, the frame keeps working. For every other code, the frame shows its own message, and your page decides what to do next.
Troubleshooting
Fix a blank frame
The SDK raisesbootstrap_timeout when the frame doesn’t answer. Usually the browser refused to show the frame, and the frame stays blank. Open the browser console and look for a frame-ancestors or frame-src error. Then check the following causes:
- The page’s origin isn’t in the session’s
origins. The origin must match exactly: the scheme, the host, and the port.https://www.example.comandhttps://example.comare different origins, and*.example.comdoesn’t coverexample.com. If you leave outorigins, check the organization’s embed domains instead. - Your page is inside another frame. Every page in the frame chain must be allowed. Add the outer pages’ origins to
ancestorOrigins. - Your page’s own Content Security Policy blocks the frame. If your page sends a
Content-Security-Policyheader, itsframe-srcorchild-srcmust allowhttps://app.sajn.se. - The page isn’t served over HTTPS. The frame never loads in a page served over
http://. - The client secret is old. A secret from a session that ended more than 24 hours ago gets a refused frame. Create a new session on every
fetchClientSecretcall. - The
hostoption points at the wrong sajn host. Leavehostout to usehttps://app.sajn.se. Sandboxes use the same host. - The network is slow, or something blocks the frame, such as a browser extension or a corporate proxy. To give a slow network more time, raise
bootstrapTimeoutMs.
Fix other problems
- The frame shows an error message: read the
codeinonErrorand see Error codes. - The frame is cut off or has no height: in
fixedmode, give the container a height. Ininlinemode, don’t. - An eID window doesn’t open: the browser blocked it. The party can use the link that the button turns into.
POST /api/v1/embed-sessionsfails: logcodeandrequestId, and see Errors.
Embed in a mobile app
In a React Native app, use@sajn/embed-react-native. It runs the signing page in a web view and opens eID pages and the BankID app for you. In Swift or Kotlin, the same guide shows how to wire a web view by hand.
If you’d rather not embed, show your page in the system browser: SFSafariViewController or ASWebAuthenticationSession on iOS, or Custom Tabs on Android. Every eID works there as on any web page.
Accessibility
The signing frame targets WCAG 2.2 level AA. sajn tests it with axe in light and dark mode: the signing view, form fields, the menu, the reject dialog, view mode, and the receipt after signing. The party can sign with the keyboard alone, and focus is always visible. Your appearance settings take part in this. sajn rejects a text color and background pair below a 4.5:1 contrast ratio, both on the session and inupdate().
Security
- The party’s signing token never reaches the browser. The frame redeems the client secret once and keeps a short-lived credential in memory.
- Only the pages in the session’s
originsandancestorOriginscan show the frame, and only while they match the organization’s embed domains. The page that talks to the frame must be inorigins. - Rotating or deleting the API key that created a session ends the session.
- The SDK and the frame check the origin of every message, and post only to each other’s origin.
- Features and origins can’t change from the browser.
Next steps
JavaScript
Use
createSigningEmbed in any web page.React
Use the React component.
Vue
Use the Vue component.
Migrate from embed v1
Move from SDK 0.x and signing tokens to embed sessions.

