Skip to main content
Mint a per-signer signing session and mount the Lumin signing UI inline with @luminpdf/lumin-embed-signing-sdk. For redirect flows, use Get Signing Link instead.

When to use embedded signing

  • Signers are already authenticated in your app and you want an in-context experience.
  • You control when and how the signing prompt appears (modal, inline panel, dedicated step).
  • You are building for web or mobile webview, not a native-only flow without a hosted shell page.
Lumin does not authenticate signers in embedded flows. Your app is responsible for verifying that the person opening the signing surface is the intended recipient.

Prerequisites

Before you integrate embedded signing, make sure you have:
  • An API key or OAuth 2.0 app with the sign:requests scope. Create these yourself in Settings → Developer settings in the Lumin app. See API Keys or OAuth 2.0.
  • An embedding domain allow-listed for that API key or OAuth app. Embedded signing only works when your host page origin matches an allow-listed domain.
  • Signature requests created through the public API (POST /signature_request/send or POST /signature_request/send-from-template). In-app agreements created from the Lumin UI are not embeddable.
Embedding domains are currently allow-listed by Lumin on request. Contact Lumin Support with your Workspace ID/Workspace name and desired embedding domain to request allow-listing for your domain. You cannot mint signing sessions until your domain is allow-listed.

Workflow overview

Webhooks are the source of truth for server-side state. SDK events are for host-app UX only, do not persist business state from client-side events alone.

Integration steps

1

Set up credentials and domain

Create an API key or OAuth app in Settings → Developer settings (API Keys, OAuth 2.0). Then contact Lumin Support to allow-list embedding domain registration for API key/OAuth client.
2

Create a signature request

From your backend, send the document with POST /signature_request/send or POST /signature_request/send-from-template. See Send Signature Request.
3

Mint a signing session

Call POST /signature_request/{id}/signing-session with the signer’s email and pass the returned sign_url to your frontend. See Create Signing Session.
4

Embed the signing UI

Install @luminpdf/lumin-embed-signing-sdk, initialize the client once, then call client.open({ signUrl }) when the signer is ready.
5

Handle completion

Use SDK events for immediate UX feedback. Use webhooks as the source of truth for server-side state.

API examples

Create a signature request

curl -X POST https://api.luminpdf.com/v1/signature_request/send \
  -H "Authorization: API-key <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Rental Agreement",
    "file_url": "https://example.com/rental-agreement.pdf",
    "signing_type": "SAME_TIME",
    "expires_at": 1927510980694,
    "signers": [
      { "email_address": "[email protected]", "name": "Alex Tenant" }
    ]
  }'
Save the signature_request_id from the response. If the status is WAITING_FOR_PROCESSING, poll GET /signature_request/{id} until the request is ready to sign.

Mint a signing session

curl -X POST https://api.luminpdf.com/v1/signature_request/696d007913f3b8.../signing-session \
  -H "Authorization: API-key <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "signer_email": "[email protected]",
    "expiry": 900000
  }'
{
  "sign_url": "https://sign.luminpdf.com/embed?session=8647b08b...",
  "signer_email": "[email protected]",
  "expires_at": 1927510980694,
  "status": "NEED_TO_SIGN"
}
Do not use Get Signing Link for iframe embeds. Do not expose your API key to the browser.
Mint the session when the signer is ready to sign. Each sign_url is single-use and expires after expiry milliseconds. See Create Signing Session for request and response fields.

SDK example

npm install @luminpdf/[email protected]
import { LuminSigning } from "@luminpdf/lumin-embed-signing-sdk";

const client = new LuminSigning({ clientId: "your-lumin-app-id" });

client.on("loaded", () => { /* signing surface ready */ });
client.on("signed", () => { /* update UX — wait for webhook */ });
client.on("error", (event) => { console.error(event.code, event.message); });

const session = client.open({
  container: "#sign-root",
  signUrl: signUrlFromBackend,
});
<div id="sign-root" style="width: 100%; min-height: 600px;"></div>

Events and webhooks

EventWhen it firesRecommended action
loadedSigning UI is readyHide your loading state
signedSigner completed signingShow a thank-you state; wait for webhook
declinedSigner declinedShow a message; wait for webhook
expiresign_url TTL elapsedMint a new session from your backend
errorSigning abortedSee Domain verification for common codes
closeIframe torn downFinal cleanup
Listen for signature_request_signed and signature_request_approved webhooks to advance your workflow. See the Webhooks guide.

Domain verification

Embedded signing validates the parent page origin when the iframe loads. The signing surface renders only when the origin matches a domain allow-listed for the API key or OAuth app that minted the session. Common error codes related to domains and sessions:
CodeMeaning
origin_not_allowedParent origin is not on your app’s allow-list
session_expiredsign_url was used after its TTL — mint a new session
session_already_usedThe same sign_url was mounted twice — mint a new session
signature_request_no_longer_activeRequest cancelled or expired while iframe was open

Mobile webview

Native apps must load the SDK through a hosted shell page on your verified HTTPS domain (for example, https://app.example.com/sign-shell). You cannot bundle the SDK as a static file:// asset, the origin check will fail. Pass sign_url from your native backend into the webview shell via a query parameter or JavaScript bridge. The shell mounts the SDK the same way a desktop browser would.

Email delivery

Embedded signing does not automatically suppress Lumin email notifications. Signers may still receive email from Lumin.