← LumenSign API reference Deutsche Fassung

Embedded signing

Your customers sign directly in your application: you show our signing page in an iframe on your own website. This works at every level where external recipients act (SES, AES), and for approvers and viewers. Qualified signatures (QES) are never embedded: members with a certificate sign in the dashboard or through the approval link.

This guide is the English version of the German guide embedded-signing and covers the same steps.

Your business logic still relies on the webhook (or GET /v1/events). The messages of the embedded page are hints for your user interface, not a confirmation.

1. Set the allowed websites

Only websites on the list of your organization may embed the signing page. The list is empty until you fill it, and empty means embedding is off.

PUT /v1/embed-settings
Authorization: Bearer qgw_live_…
Idempotency-Key: 5d1f…
Content-Type: application/json

{ "allowedOrigins": ["https://app.example.com", "https://*.example.org"] }

Response:

{ "object": "embed_settings", "allowedOrigins": ["https://app.example.com", "https://*.example.org"] }

Rules for every entry:

Changes apply immediately to our signing page and to new embedded sessions. For the signing step of SES/AES, the signature service receives the list when the signing round starts; a later change only affects new rounds. There, only exact origins count (entries with * allow only our page, not the signing step), and at most nine of your origins per round.

The same rule applies per mode to the signing page, embedded sessions and the signing step: in live mode only the https entries count, and http://localhost:<port> and other test-only entries count in test mode only. If embedding is switched off for your organization (tier feature) or still blocked in live mode, the signing step receives none of your origins either. Qualified signatures (QES) are never embedded.

2. Request an embedded session

As soon as a recipient is up next (the event recipient.ready), your server requests a session for the website on which you show the page:

POST /v1/envelopes/{id}/recipients/{rid}/embedded-session
Authorization: Bearer qgw_test_…
Idempotency-Key: 8c2e…
Content-Type: application/json

{ "origin": "https://app.example.com", "locale": "en" }
{
  "object": "embedded_session",
  "url": "https://sign.<domain>/s/<token>?embed=1",
  "expiresAt": "2026-09-30T12:05:00Z",
  "origin": "https://app.example.com"
}

Possible errors, checked in this order:

Status / codeMeaning
404The envelope or recipient does not belong to your organization or not to the mode of the key
403 / 9039Embedding is switched off for your organization
403 / 9038origin is not on the list (also: a wildcard instead of an exact origin, http://localhost in live mode)
422 / 9012 qes_not_embeddableThe recipient signs qualified (QES)
409 / 9033The recipient is not up next (details.reason)
409 / 9030 signing_page_disabledThe signing page is switched off
403 / 9030 embedding_live_disabledLive key: embedding is not yet released for live use
403The key lacks envelopes:signing_urls

A signing link (POST …/signing-url) or the link of the invitation email can be embedded as well: append ?embed=1. Then every website on your list may show the page, and the link works as usual (15 minutes until first opened, then a 60-minute session). The embedded session is preferable because it is bound to exactly one website.

3. Show the page with the widget

<div id="signature"></div>
<script src="https://sign.<domain>/embed/v1/onesign.js"></script>
<script>
  const widget = OneSign.mountSigning(document.getElementById('signature'), {
    url: sessionUrl, // the URL from step 2, from your server
    minHeight: 600,
    title: 'Sign the rental agreement',
    onEvent(event) {
      if (event.type === 'completed') showCompletion();
      if (event.type === 'declined') showDeclined();
      if (event.type === 'error') showError(event.code);
    },
  });
  // widget.destroy() removes the iframe again.
</script>

The widget has no dependencies and sets no cookies. It creates the iframe with allow="camera; microphone; fullscreen" (the identity check of AES needs the camera at every frame level) and referrerpolicy="no-referrer", adjusts the height (resize) and passes on only messages that come from the origin of the signing page and match the protocol.

Without the widget: your own iframe with the same attributes works just as well; then listen for message yourself and check event.origin against the origin of the URL, as well as source and v of the message.

4. Messages of the page

Every message has the form { source: "onesign", v: 1, type, …, at } and never contains names, email addresses, tokens or links. The page sends them only to the bound origin (or the checked website of your list), never to *.

typeAdditional fieldsMeaning
readyenvelopeId, recipientIdPage loaded
resizeheightHeight of the page in pixels
signedenvelopeId, recipientIdThe recipient has signed
approvedenvelopeId, recipientIdThe recipient has approved
viewedenvelopeId, recipientIdThe recipient has confirmed viewing
declinedenvelopeId, recipientIdThe envelope was declined (also in the signing step)
completedenvelopeIdThe envelope is complete
expiredenvelopeId, recipientIdThe envelope has expired
errorcodelink_invalid, rate_limited, unavailable, action_failed, envelope_cancelled, envelope_failed, qes_new_window

envelopeId and recipientId appear only in messages of an embedded session; with a signing link and ?embed=1 they are missing. In embedded mode the page never redirects on its own: react to completed (or the webhook). The privacy notice and the footer with the legal notice stay visible in the frame.

5. Security and browsers