---
title: "Embedded signatures integration"
description: "How to embed Signer signing and approval in any application with the LacunaSignerWidget component, which needs a Web PKI license for the page domain."
canonical: "https://docs.lacunasoftware.com/en-us/articles/signer/embedded-signature"
locale: "en"
product: "Signer"
last_updated: "2026-10-22"
---

# Embedded signatures integration

## Introduction

Embedded Signature Integration allows you to sign/approve documents in Signer within any application through the component `LacunaSignerWidget`.

> **Note**
>
> Signer must be configured with a Web PKI license that has the same domain in which the component will be loaded.

## Installation

First, include the latest [lacuna-signer-widget.js](https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js) file on your page:

```html
<script type="text/javascript" src="https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js"
    integrity="sha256-kS96fv3kyz1iJOP3crrI/2dvBzAgEHu4dZF9ecS/f0I="
    crossorigin="anonymous"></script>
```

Instantiate the class `LacunaSignerWidget`:

```javascript
var widget = new LacunaSignerWidget();
```

Then call the `on()` method passing the event type you want to listen and a callback function that will be called when the user signs/approves the document:

```javascript
//signing
widget.on(widget.events.documentSigned, function (e) {
	// ...
	//alert('Document ' + e.id + ' signed');
});
```

```javascript
//approval
widget.on(widget.events.documentApproved, function (e) {
	// ...
	//alert('Documento ' + e.id + ' approved');
});
```

If you prefer, use the `listen()` method to listen to all events with just one callback:

```javascript
widget.listen(function (e) {
	if (e.type === widget.events.documentSigned) {
		// ...
		//alert('Document ' + e.id + ' signed');
	} else if (e.type === widget.events.documentApproved) {
		// ...
		//alert('Documento ' + e.id + ' approved');
	}
});
```

> **Note**
>
> Always check in the backend whether the document is actually completed because callbacks can be triggered by other javascript files.

Finally, load the iFrame using the `render()` method which needs two mandatory inputs:

* `embedUrl`: it is the URL related to the action you want to take. Corresponds to the embedUrl parameter returned by [API Action URL](https://www.dropsigner.com/swagger/index.html#operations-Documents-post_api_documents__id__action_url)
* `elementId`: it is the element's id in which the signature component will be displayed.

And two optional inputs:

* `page`: page number where the document will open on startup (to open the last page of any document, no matter its size, assign the number `'-1'`).
* `scale`: the percentage that will define at what scale the document will be opened. It can be defined through a simple string `'100'` or through the `widget.scaleOptions.S100`

```javascript
widget.render(embedUrl, 'embed-container', page, scale);
```

If you wish, the DOM element can be provided directly:

```javascript
widget.renderElement(embedUrl, document.getElementById('embed-container'), page, scale);
```

## Online Example

To try it, open [Sign without leaving your page](https://try.lacunasoftware.com/en/signer/embedded-signature) on the Demo Site, which
embeds Dropsigner's signing page with this widget. Its [Markdown version](https://try.lacunasoftware.com/en/signer/embedded-signature.md) lists
every call it makes. 

The page below is the browser side of an embedded signature. The button asks your own backend for an embed URL, and your backend,
which keeps the API key away from the browser:

1. Uploads the PDF (`POST /api/uploads`) and creates the document with the signer as a participant and
   `disablePendingActionNotifications: true`, so nobody is e-mailed (`POST /api/documents`).
2. Asks for that participant's action URL (`POST /api/documents/{id}/action-url`) and returns its `embedUrl`.

See [Signing in your own application](https://docs.lacunasoftware.com/en-us/articles/signer/integration/documents#signing-in-your-own-application) for both calls. 
The script calls the backend with jQuery's `$.post`, so load jQuery on the page, or use `fetch` instead.

> **Caution**
>
> In the `POST /api/documents/{id}/action-url` route, `{id}` is the document ID, not the ID of the participant's action (`flowActionId`). With the action ID in the route, the API responds with the `DocumentNotFound` error. Send `flowActionId` in the request body, and only when the participant has more than one pending action on the document. The action IDs are in `flowActions[].id`, in the response of `GET /api/documents/{id}`.

```html
<script src="https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js"></script>
<div style="text-align: center;">
  <h1>
  Signer embedded signature
  </h1>
  <button type="button" onclick="startSignature(this)">
  Start Signature for Alan Turing (560.723.861-05)
  </button>
</div>

<div id="embed-container" class="frame-container">

</div>
```

```css
.frame-container {
  position: relative;
  width: 100%;
  height: 0;
  padding-bottom: 56.25%;
}

.frame-responsive {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  border: none;
}
```

```javascript
function sign(embedUrl) {
	var widget = new LacunaSignerWidget();

	widget.on(widget.events.documentSigned, function (e) {
		// ...
		alert('Document ' + e.id + ' signed');
	});

	widget.render(embedUrl, 'embed-container');
}

// Replace with your own call: your backend creates the document and returns the embed URL
function startSignature(elem) {
	elem.innerHTML = 'Loading ...';

	$.post("/your-backend/embedded-signature", {}).
		done(function (data) {
			if (data) {
				sign(data);
				elem.style.visibility = "hidden";
			} else {
				elem.innerHTML = 'Error';
			}
	}).fail(function () {
		elem.innerHTML = 'Error';
	});
}
```

Sign with the Lacuna test certificate of Alan Turing (CPF 560.723.861-05), from [Test certificates](https://docs.lacunasoftware.com/en-us/articles/pki-guide/test-certs), on an
instance that trusts the Lacuna Test PKI. If Web PKI is not installed, the widget sends the user to its installation page, which brings them
back to your application at the end.

## Online example of cloud signing

The BirdID test certificate will be used (CPF 026.102.470-10, see the instructions below for pairing).

The page is the same as in the [online example](https://docs.lacunasoftware.com/en-us/articles/signer/embedded-signature#online-example), with another title and button; the backend creates the document for the
BirdID test user instead, who signs with a cloud certificate. 

```html
<script src="https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js"></script>
<div style="text-align: center;">
  <h1>
  Signer embedded Cloud signature
  </h1>
  <button type="button" onclick="startSignature(this)">
  Start Signature for Usuario Teste BIRDID (026.102.470-10)
  </button>
</div>

<div id="embed-container" class="frame-container">

</div>
```

```javascript
function sign(embedUrl) {
	var widget = new LacunaSignerWidget();

	widget.on(widget.events.documentSigned, function (e) {
		// ...
		alert('Document ' + e.id + ' signed');
	});

	widget.render(embedUrl, 'embed-container');
}

// Your backend creates the document for the BirdID test user and returns the embed URL
function startSignature(elem) {
	elem.innerHTML = 'Loading ...';

	$.post("/your-backend/embedded-signature?cloud=true", {}).
		done(function (data) {
			if (data) {
				sign(data);
				elem.style.visibility = "hidden";
			} else {
				elem.innerHTML = 'Error';
			}
	}).fail(function () {
		elem.innerHTML = 'Error';
	});
}
```

To sign as the test user of the BirdID test (homologation) environment, scan the QR code below in the BirdID app:

![BirdID HML credentials](https://docs.lacunasoftware.com/images/signer/birdidhml.png)

## Additional settings

When instantiating the `Lacuna Sign Widget` class you can pass an object with one or more settings listed below:

Atributo                 | Tipo         |  Descrição  
-----------------------  | ------------ | ----------
`enableNotifications`    | boolean      | Notifications are not sent  after completing an embedded signature/approval. Set this parameter to `true` to send notifications.
`disableDocumentPreview` | boolean      | If `true`, it will hide the document preview. In this case, only the signing button and signer/approver data will be displayed. You can set this parameter after instantiation using `setDisableDocumentPreview` method.
`enableRefusal`          | boolean      | If `true`, it will display the option to decline to sign a document. You can set this parameter after instantiation using `setEnableRefusal` method.
`culture` | string | Defines the widget language, the options available are `"pt"` (portuguese), `"en"`(english) and `"es"`(spanish).
`theme` | string | Defines the widget theme with the 3-character string (e.g. Theme: amazon-cornell-red - Code:`"acr"`). The themes are available at [the themes page](https://docs.lacunasoftware.com/en-us/articles/signer/on-premises/customization/temas)

Example:

```javascript
var widget = new LacunaSignerWidget({ disableDocumentPreview: true });
```

```javascript
var widget = new LacunaSignerWidget();

widget.setDisableDocumentPreview(true);
```

## Online example of signature ​without preview

The same page as in the [online example](https://docs.lacunasoftware.com/en-us/articles/signer/embedded-signature#online-example), with `disableDocumentPreview: true` and the widget in a container at most 600 pixels
wide. 

```html
<script src="https://cdn.lacunasoftware.com/libs/signer/lacuna-signer-widget-0.8.0.min.js"></script>
<div style="text-align: center;">
  <h1>
  Signer embedded signature without Preview
  </h1>
  <button type="button" onclick="startSignature(this)">
  Start Signature for Alan Turing (560.723.861-05)
  </button>
</div>

<div class="container">
  <div id="embed-container" class="frame-container">

  </div>
</div>
```

```css
.container {
  max-width: 600px;
  height: 100%;
  margin-left: auto;
  margin-right: auto;
}
```

```javascript
function sign(embedUrl) {
	var widget = new LacunaSignerWidget({ disableDocumentPreview: true });

	widget.on(widget.events.documentSigned, function (e) {
		// ...
		alert('Document ' + e.id + ' signed');
	});

	widget.render(embedUrl, 'embed-container');
}

// Replace with your own call: your backend creates the document and returns the embed URL
function startSignature(elem) {
	elem.innerHTML = 'Loading ...';

	$.post("/your-backend/embedded-signature", {}).
		done(function (data) {
			if (data) {
				sign(data);
				elem.style.visibility = "hidden";
			} else {
				elem.innerHTML = 'Error';
			}
	}).fail(function () {
		elem.innerHTML = 'Error';
	});
}
```

## Example for mobile devices

Alternatively, it is possible to use embedded signatures in mobile devices, an example app is available at our [GitHub repository](https://github.com/LacunaSoftware/signer-flutter). The example displays a web page containing the embedded signature widget in JavaScript inside your mobile phone. The application was designed with [Flutter](https://flutter.dev/).

The following image displays a signature example without the document preview:

![Mobile embedded example without preview](https://docs.lacunasoftware.com/images/signer/flutter/signer_without_preview_theme.png)

## Signature marks positioning

From Signer version 1.70.0 and version 0.7.0 of the Widget, it is possible to embed the signature marks positioning page in other applications as it was already done
for the signature page. Once the marks are positioned, the position information will be retrievable through the Widget as well as Signer API in order to create one or
more documents with the selected positions.

The first step is to create a marks positioning session using the session creation API. There are two variants of the API:

* [Basic session creation API](https://www.dropsigner.com/swagger/index.html#operations-MarksSessions-post_api_marks_sessions): creates a session by requiring only necessary information. 
Signer personal information such as e-mail and country identifier for instance are optional. Use this API if, after obtaining the session result, you still intend to modify the request data
before sending a create document request to Signer.

* [Session creation with document create request API](https://www.dropsigner.com/swagger/index.html#operations-MarksSessions-post_api_marks_sessions_documents): 
creates a session by using the same request that is sent to create documents. Use this API if you prefer to use the session result without any modifications when using the create documents 
API.

The result of both APIs will be the return of a session ID and a URL to embed the positioning page:

```javascript
{
	//Session ID
	"id": "4cdcfa93-8f38-46f9-8634-246d5589fe5f",
	//URL to use with the Widget
	"embedUrl": "https://www.dropsigner.com/position-marks/4cdcfa93-8f38-46f9-8634-246d5589fe5f?v=1"
}
```

Once the session URL is obtained, initialize the Widget the same way it was done for the signature page mode:

```javascript
var widget = new LacunaSignerWidget();
```

Call the `on()` method passing the marks positioned event type and a callback function that will be called when the user finishes positioning marks:

```javascript
//assinatura
widget.on(widget.events.marksPositioned, function (e) {
	// The data attribute will contain the request with the marks positions defined
	//console.log(e.data);
	//alert('Marks positioned');
});
```

Finally, load the iFrame using the `render()` method:

```javascript
widget.render(embedUrl, 'embed-container');
```

Once the positioning session is concluded, each entry of the `flowActions` attribute from the request provided when creating the session will be updated with the selected positions 
(more specifically - the `prePositionedMarks` attribute will be filled accordingly).

The updated request can be obtained by the `data` attribute of the marks positioned event or by the attribute of same name in the 
[get session data API](https://www.dropsigner.com/swagger/index.html#operations-MarksSessions-get_api_marks_sessions__id_).

> **Note**
>
> The positioning session has a duration of 1 hour after its creation. After that interval, it won't be posible to retrieve nor update session data.

**Demos and samples**

- Live demo: [Sign without leaving your page](https://try.lacunasoftware.com/en/signer/embedded-signature)
- Sign without leaving your page (embedded signature): [.NET](https://github.com/LacunaSoftware/SignerSamples/blob/master/dotnet/console/Console/Scenarios/EmbeddedSignatureScenario.cs) · [Java](https://github.com/LacunaSoftware/SignerSamples/blob/master/java/console/src/main/java/com/lacunasoftware/signer/sample/scenarios/EmbeddedSignatureScenario.java) · [Node.js](https://github.com/LacunaSoftware/SignerSamples/blob/master/nodejs/scenarios/embeddedSignatureScenario.ts) · [PHP](https://github.com/LacunaSoftware/SignerSamples/blob/master/php/Scenarios/EmbeddedSignatureScenario.php)
