# Task: Firma integrada

I want my application to do what Lacuna's "Firma integrada" demo does: Integre la pantalla de firma en su propio sistema con el widget de firma de Signer: el firmante nunca sale de su página y no recibe ninguna invitación por correo electrónico.

- Docs: https://docs.lacunasoftware.com/en-us/articles/signer/embedded-signature
- Live demo: https://try.lacunasoftware.com/es/signer/embedded-signature
- This demo for AI agents: https://try.lacunasoftware.com/es/signer/embedded-signature.md

## Samples

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

## Call sequence

Recorded on 2026-10-12 against the real products with Lacuna's public test certificates and data. Keys, tokens, sessions, ids, file contents and personal data are masked.

### 1. Dropsigner takes the PDF and creates the document for the signer with notifications off, so nobody is emailed, then the document is read back and its signer compared with the one sent.

On the server: `SignerClient.UploadFileAsync, then SignerClient.CreateDocumentAsync, then SignerClient.GetDocumentDetailsAsync`

`POST https://www.dropsigner.com/api/uploads` → 200

Request:

```json
"<multipart/form-data, 2138 bytes>"
```

Response:

```json
{
  "location": "/api/uploads/00000000-0000-0000-0000-000000000000?access_ticket={value}&culture=en&contentType={value}",
  "id": "00000000-0000-0000-0000-000000000000",
  "name": "Lacuna-Test-Contract.pdf",
  "contentType": "application/pdf"
}
```

`POST https://www.dropsigner.com/api/documents` → 200

Request:

```json
{
  "AreFilesScanned": false,
  "Files": [
    {
      "DisplayName": "[Lacuna Demo Site · binds no one] Lacuna-Test-Contract",
      "Id": "00000000-0000-0000-0000-000000000000",
      "Name": "Lacuna-Test-Contract.pdf",
      "ContentType": "application/pdf"
    }
  ],
  "IsEnvelope": false,
  "FlowActions": [
    {
      "User": {
        "Name": "Alan Mathison Turing",
        "Identifier": "***.***.***-**",
        "Email": "someone@example.com"
      },
      "Type": "Signer",
      "Step": 1,
      "AllowElectronicSignature": true
    }
  ],
  "DisablePendingActionNotifications": false,
  "DisableNotifications": true,
  "ForceCadesSignature": false,
  "SignatureType": 0,
  "ExpirationDate": "2026-10-12T00:00:00"
}
```

Response:

```json
[
  {
    "uploadId": "00000000-0000-0000-0000-000000000000",
    "documentId": "00000000-0000-0000-0000-000000000000"
  }
]
```

`GET https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000` → 200

Response:

```json
{
  "checksumMd5": "<hex, 16 bytes>",
  "isDeleted": false,
  "flowActions": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "shouldNotarize": false,
      "creationDate": "2026-10-12T02:08:02.7520479+00:00",
      "pendingDate": "2026-10-12T02:08:02.7522816+00:00",
      "updateDate": "2026-10-12T02:08:02.7522813+00:00",
      "user": {
        "id": "00000000-0000-0000-0000-000000000000",
        "name": "Alan Mathison Turing",
        "identifierType": "Cpf",
        "identifier": "***.***.***-**",
        "identifiers": [
          {
            "id": "00000000-0000-0000-0000-000000000000",
            "isGrant": true,
            "type": "Cpf",
            "value": "***.***.***-**"
          }
        ],
        "email": "someone@example.com",
        "createdBySubscriptionId": "00000000-0000-0000-0000-000000000000"
      },
      "numberRequiredSignatures": 0,
      "numberRequiredActions": 0,
      "allowElectronicSignature": true,
      "requireEmailAuthenticationToSignElectronically": true,
      "requireSmsAuthenticationToSignElectronically": false,
      "requireWhatsappAuthenticationToSignElectronically": false,
      "requireAuthenticatorAppToSignElectronically": false,
      "requireSelfieAuthenticationToSignElectronically": false,
      "requireDatavalidAuthenticationToSignElectronically": false,
      "requirePixAuthenticationToSignElectronically": false,
      "requireLivenessAuthenticationToSignElectronically": false,
      "requireIdScanAuthenticationToSignElectronically": false,
      "requireCompanyCertificate": false,
      "signatureInitialsMode": "None",
      "isElectronic": false,
      "allowRuleFlowToContinueIfRefused": false,
      "allowIdrcSignature": false,
      "notificationLanguage": "Pt",
      "type": "Signer",
      "status": "Pending",
      "step": 1
    }
  ],
  "observers": [],
  "permissions": {
    "move": true,
    "editFlow": true,
    "viewSignedDocumentWithExternalFlowBeforeCompleted": true,
    "submitExternalFlowFiles": true,
    "updateDocumentVersion": true,
    "createAttachments": true,
    "deleteAttachments": true,
    "viewPrivateAttachments": true,
    "editAttachments": true,
    "createMeeting": true,
    "deleteMeeting": true,
    "refuse": true,
    "editInformation": true,
    "cancel": true,
    "editParticipantInformation": false
  },
  "pending": {},
  "hideDownloadOptionForPendingDocuments": false,
  "id": "00000000-0000-0000-0000-000000000000",
  "qrCode": "***",
  "name": "[Lacuna Demo Site · binds no one] Lacuna-Test-Contract",
  "key": "***",
  "filename": "Lacuna-Test-Contract.pdf",
  "fileSize": 5424,
  "mimeType": "application/pdf",
  "hasSignature": false,
  "status": "Pending",
  "isConcluded": false,
  "organization": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Lacuna Demo Site"
  },
  "isOwner": false,
  "creationDate": "2026-10-12T02:08:02.7520204+00:00",
  "updateDate": "2026-10-12T02:08:02.7520218+00:00",
  "expirationDate": "2026-10-13T02:59:59.9999999+00:00",
  "expirationDateWithoutTime": "2026-10-12",
  "createdBy": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "Lacuna Demo Site"
  },
  "hasExternalFlow": false,
  "forceCadesSignature": false,
  "isScanned": false,
  "isEnvelope": false,
  "signatureType": "None"
}
```

### 2. Dropsigner gives an action URL for the signer, asked for with the email read back alone, whose embed URL signs as them and is never logged or shown.

On the server: `SignerClient.GetActionUrlAsync`

`POST https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000/action-url` → 200

Request:

```json
{
  "EmailAddress": "someone@example.com",
  "RequireEmailAuthentication": false
}
```

Response:

```json
{
  "url": "***",
  "embedUrl": "***"
}
```

### 3. The Signer widget renders the embed URL in a frame, inside a closed shadow root on the Demo's page, in the page's language.

In the browser: `widget.renderElement(embedUrl, root)`

### 4. The signer signs in the frame and the widget posts documentSigned, which the Demo confirms by reading the document back; nobody signs in this recording, so it is not performed.

In the browser: `documentSigned, the frame's message`

### 5. Start over cancels the document with its reason in the page's language; notifications are off, so nobody is emailed.

On the server: `SignerClient.CancelDocumentAsync`

`POST https://www.dropsigner.com/api/documents/00000000-0000-0000-0000-000000000000/cancellation` → 200

Request:

```json
{
  "Reason": "The visitor started over in the Lacuna Demo Site."
}
```

## Patterns to copy

- The Signer widget's events are only hints: accept messages only from the frame you rendered last and from its embed URL's origin, then confirm by reading the document back. Show success only once Dropsigner reports the document Concluded and the flow action Completed, and keep a safety poll in case an event is missed.
- Treat the action URL's `EmbedUrl` as a credential: whoever opens it signs as the document's signer. Use it only if it is absolute HTTPS, never log it or put it in markup, and render the frame into a closed shadow root.
- Before asking for an action URL, compare the read-back signer with what you sent: Dropsigner keeps a known email's stored name and CPF. Ask for the action URL with the email as read back, and treat `NoPendingActionFoundForEmailAndIdentifier` as "the signer already acted".
- For an in-page signature the Demo creates the document with `DisableNotifications = true`, so nobody is emailed, and a short expiry, because an abandoned document can't be reached again.

## What to do

Implement this in my project with its existing stack, language, framework and conventions, following the call sequence and the patterns above, and adapting the samples rather than copying them whole. Ask me before adding any dependency or package. Keep API keys and private keys out of the browser and out of the repository.
