Skip to main content

Integrate Legalesign Signer into Your Website

Enable for your team

The Signer component requires activation. Contact support to enable it for your team.

The Legalesign Signer is a platform-agnostic web component that provides a complete and seamless document signing experience from within your own app. It works with vanilla HTML, React, Vue, Angular, or any web framework.

Embed this component wherever your users need to sign documents — inside a CRM, client portal, or any internal application that renders HTML.

Installation

Load directly from the CDN (no install required):

<script type="module" src="https://cdn.legalesign.io/signer/latest/ls-signer.esm.js"></script>

React installation

Install via a package manager:

npm install legalesign-signer
# or
pnpm add legalesign-signer

Basic Integration

HTML & JavaScript

<ls-signer
recipient-id="abc123"
private-key="key"
session-id="session"
></ls-signer>

<script>
const signer = document.querySelector('ls-signer');
signer.addEventListener('signingSuccess', (e) => console.log('Signed:', e.detail.eventType, e.detail.documentId));
signer.addEventListener('signingFail', (e) => console.error('Failed:', e.detail.failureReason, e.detail.error));
signer.addEventListener('fieldChange', (e) => console.log('Field event:', e.detail.eventType));
</script>

React

import { useEffect, useState } from 'react';
import { LsSigner } from 'legalesign-signer/react';
import 'legalesign-signer/react.css';

function SigningPage() {
const [signerData, setSignerData] = useState(null);

useEffect(() => {
fetch('/your-backend/to-get-token')
.then(res => res.json())
.then(setSignerData);
}, []);

if (!signerData) return <p>Loading...</p>;

return (
<LsSigner
recipientId={signerData.recipientId}
privateKey={signerData.token}
sessionId={signerData.sessionId}
onSuccess={({ eventType, documentId }) => console.log(eventType, documentId)}
onFail={({ eventType, failureReason, error }) => console.error(eventType, failureReason, error)}
onChange={({ eventType, uuid, saved, field, document }) => console.log('Event:', eventType)}
/>
);
}

React requires react and react-dom (v18 or v19) as peer dependencies.

Vue

<template>
<ls-signer
ref="signer"
:recipient-id="recipientId"
:private-key="privateKey"
:session-id="sessionId"
/>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

const signer = ref(null);

function handleSuccess(e) {
console.log('Signed:', e.detail);
}
function handleFail(e) {
console.error('Failed:', e.detail);
}

onMounted(() => {
signer.value.addEventListener('signingSuccess', handleSuccess);
signer.value.addEventListener('signingFail', handleFail);
});

onBeforeUnmount(() => {
signer.value?.removeEventListener('signingSuccess', handleSuccess);
signer.value?.removeEventListener('signingFail', handleFail);
});
</script>
tip

If using Vue with Vite, add isCustomElement: (tag) => tag.startsWith('ls-') to your Vue plugin config to suppress unknown element warnings.

warning

Vue 3 lowercases event names on custom elements, so @signingSuccess won't work. Use addEventListener on the element ref as shown above.

Nuxt 3

Wrap <ls-signer> in <ClientOnly> to prevent SSR errors. Add the custom element config in nuxt.config.ts:

export default defineNuxtConfig({
vue: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('ls-'),
},
},
});

Authentication

The signer component requires a short-lived token from your backend. There are two options:

  1. GraphQL generateComponentToken — Call with component: LS_SIGNER and either a recipientId or sessionId in the signer scope (provide one, not both).
  2. REST API — Call GET /signer/{signerId}/component-token/ with your API key.

Both return the token and sessionId needed by the component. See Widget Authorization for the full server-side token flow.

Option 1: GraphQL (Node.js)

Using recipientId (most common — use when you know the recipient but don't yet have a session):

const response = await fetch('https://graphql.uk.legalesign.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LEGALESIGN_API_KEY}`,
},
body: JSON.stringify({
query: `mutation MintSignerToken($input: GenerateComponentTokenInput!) {
generateComponentToken(input: $input) {
token
tokenType
sessionId
expiresIn
expiresAt
}
}`,
variables: {
input: {
component: 'LS_SIGNER',
signer: { recipientId: '<recipient-id>' }
}
}
})
});

const { data } = await response.json();
const { token, sessionId } = data.generateComponentToken;

Alternatively, if you already have a sessionId from a previous call:

variables: {
input: {
component: 'LS_SIGNER',
signer: { sessionId: '<session-id>' }
}
}

Option 2: REST API

const response = await fetch(
`https://eu-api.legalesign.com/api/v1/signer/${signerId}/component-token/`,
{
headers: {
Authorization: `Bearer ${process.env.LEGALESIGN_API_KEY}`,
},
}
);

const { token, sessionId } = await response.json();

Pass token as private-key and sessionId as session-id to the component.

Required Attributes

AttributeTypeDescription
recipient-idstringRecipient identifier (base64, rec prefix, or UUID)
private-keystringToken returned by generateComponentToken
session-idstringSession ID returned by generateComponentToken

Optional Attributes

AttributeTypeDefaultDescription
colourstringblueTheme colour name (see Theming)
stylestring``Style alterations (see Customer CSS)
brandingbooleantrueRemove Legalesign branding
languagestringForce a specific language and hide the language switcher (see Internationalisation)

Events

Web component eventReact propeventTypeWhen
signingSuccessonSuccess"success"Document signed successfully
signingFailonFail"failure"Signing failed, rejected, expired, cancelled, deleted, or removed
fieldChangeonChange"ready"Document and images fully loaded
fieldChangeonChange"save"A field value was saved
fieldChangeonChange"select"A field was selected or focused

signingSuccess / onSuccess

{
eventType: 'success';
documentId: string;
recipientId: string;
}

signingFail / onFail

{
eventType: 'failure';
failureReason: FailureReason;
error: string;
documentId?: string;
recipientId: string;
}
failureReasonCause
documentExpiredDocument is expired or expiry date is in the past
sessionExpiredAPI returned 401 or 403
cancelledDocument state is "cancelled"
deletedDocument has been deleted
rejectedRecipient rejected the document
removedAPI returned 404
signingErrorAny other error during load or signing

fieldChange / onChange

All three eventType values share the same event. Use eventType to distinguish:

// eventType: 'ready' — document fully loaded
{ eventType: 'ready'; document: { documentId, name, state, recipientId, pageCount, expires?, isApprover, isWitness, canReassign, doOfferReject } }

// eventType: 'save' — field value saved
{ eventType: 'save'; uuid: string; saved: boolean; field: Record<string, any> }

// eventType: 'select' — field selected or focused
{ eventType: 'select'; uuid: string; field: Record<string, any> }

React Props

The React component uses camelCase props and callback props instead of DOM events:

PropTypeRequiredDescription
recipientIdstringRecipient identifier
privateKeystringPrivate key from the generateComponentToken mutation
sessionIdstringSession identifier
colourLsSignerColourTheme colour
brandingbooleanShow Legalesign branding. Defaults to true.
languagestringForce a specific language (see Internationalisation)
onSuccess(data: { eventType: 'success'; documentId: string; recipientId: string }) => voidCalled on successful signing
onFail(data: { eventType: 'failure'; failureReason: FailureReason; error: string; documentId?: string; recipientId: string }) => voidCalled on signing failure
onChange(data: { eventType: 'ready' | 'save' | 'select'; document?: DocumentSummary; uuid?: string; saved?: boolean; field?: Record<string, any> }) => voidCalled on field events and document ready

Catch-all handler (debugging & prototyping)

Since every event includes eventType, you can wire a single function to all events:

React:

const handleSignerEvent = (e) => {
switch (e.eventType) {
case 'success': console.log('Signed:', e.documentId); break;
case 'failure': console.log('Failed:', e.failureReason, e.error); break;
case 'ready': console.log('Ready:', e.document?.name); break;
case 'save': console.log('Saved field:', e.uuid, e.saved); break;
case 'select': console.log('Selected field:', e.uuid); break;
}
};

<LsSigner
onSuccess={handleSignerEvent}
onFail={handleSignerEvent}
onChange={handleSignerEvent}
/>

Web component:

const handleSignerEvent = (e) => {
switch (e.detail.eventType) {
case 'success': console.log('Signed:', e.detail.documentId); break;
case 'failure': console.log('Failed:', e.detail.failureReason, e.detail.error); break;
case 'ready': console.log('Ready:', e.detail.document?.name); break;
case 'save': console.log('Saved field:', e.detail.uuid, e.detail.saved); break;
case 'select': console.log('Selected field:', e.detail.uuid); break;
}
};

signer.addEventListener('signingSuccess', handleSignerEvent);
signer.addEventListener('signingFail', handleSignerEvent);
signer.addEventListener('fieldChange', handleSignerEvent);

Theming

Set a colour theme with the colour prop. All shades are derived automatically.

Available Colours

pink · blue · purple · indigo · teal · green · lightblue · burnt · aubergine · red · yellow · cyan · lime · trueGreen

Omit the prop for the default blue.

<ls-signer colour="pink" ...></ls-signer>
<LsSigner colour="pink" ... />

The colour prop sets a data-ls-theme attribute on the component root. CSS custom properties define a 10–100 shade scale for each colour:

ShadeUsage
10Light backgrounds, subtle fills
20Subtle borders
30Focus rings
60Primary colour (buttons, links, active states)
70Hover state
80Dark/strong accents

Custom CSS

The component exposes CSS custom properties that can be overridden to customise appearance beyond the colour theme. This set of properties is limited at present, contact us for more.

<ls-signer
style="--ls-font-family: 'Inter', sans-serif; --ls-color-primary-60: #e91e63;"
recipient-id="abc123"
private-key="key"
session-id="session"
></ls-signer>

Available Properties

PropertyDefaultDescription
--ls-font-family'IBM Plex Sans', sans-serifPrimary font family
--ls-color-primary-10 to --ls-color-primary-100Full primary colour scale (overrides theme)
--ls-color-error#f64a44Error state colour
--ls-color-error-light#fff0f0Error background
--ls-color-success#46dbaaSuccess state colour
--ls-color-success-light#effff9Success background
--ls-color-warning#fad232Warning state colour
--ls-color-warning-light#fffcefWarning background
--ls-color-border#d8d9dcDefault border colour
--ls-color-border-subtle#e0e2e5Subtle border colour
--ls-color-bg-subtle#f7f8faSubtle background colour

Note: For full custom CSS injection (targeting internal elements directly, overriding font sizes, border radius, spacing, etc.), this is a planned future feature. Please get in touch if you're interested.

Internationalisation

The component supports 17 languages with translations bundled into the build. Language is detected automatically from the browser. A built-in language switcher in the signing UI allows the recipient to change language at any time.

Set the language attribute to force a specific language. When provided, the built-in language switcher is hidden and browser detection is bypassed.

<ls-signer language="fr" recipient-id="abc123" private-key="key" session-id="session"></ls-signer>
<LsSigner language="fr" recipientId="abc123" privateKey="key" sessionId="session" />

Priority order: language prop → browser language → English fallback.

CodeLanguageCodeLanguage
enEnglishnlDutch
frFrenchfiFinnish
bgBulgarianitItalian
esSpanishheHebrew
deGermansvSwedish
gsScottish GaeliccyWelsh
arArabicisIcelandic
elGreekiwHebrew (legacy)
ptPortuguese
roRomanian

CDN Versioning

The CDN URL supports both latest and pinned version numbers:

<!-- Always get the most recent version (recommended) -->
<script type="module" src="https://cdn.legalesign.io/signer/latest/ls-signer.esm.js"></script>

<!-- Pin to a specific version -->
<script type="module" src="https://cdn.legalesign.io/signer/v1.1.0/ls-signer.esm.js"></script>

We recommend using latest — this ensures your integration automatically receives bug fixes, security patches, and new features. Use a pinned version if you need to lock to a known working release during a QA freeze or controlled rollout. See the full version history on npm for available versions.

Complete Example

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document Signing</title>
<script type="module" src="https://cdn.legalesign.io/signer/latest/ls-signer.esm.js"></script>
</head>
<body>
<ls-signer
recipient-id="abc123"
private-key="key"
session-id="session"
colour="teal"
></ls-signer>

<script>
const signer = document.querySelector('ls-signer');

signer.addEventListener('signingSuccess', (event) => {
console.log('Document signed:', event.detail.documentId);
window.location.href = '/thank-you';
});

signer.addEventListener('signingFail', (event) => {
console.error('Signing failed:', event.detail.failureReason, event.detail.error);
});

signer.addEventListener('fieldChange', (event) => {
if (event.detail.eventType === 'ready') {
console.log('Ready to sign:', event.detail.document.name);
}
});
</script>
</body>
</html>

Browser Support

  • Chrome/Edge (latest)
  • Firefox (latest)
  • Safari (latest)
  • Mobile browsers (iOS Safari, Chrome Mobile)

Resources

Video: Migrating from iframe to the Signer component