The `pandadoc-signing` can be used to embed the PandaDoc Document Signing experience in your web application.
The pandadoc-signing library provides an easy way to embed the PandaDoc Document Signing experience in your web application using Session id.
- Embedded Document Signing
- Session ID authentication (simplified setup)
- Event handling with type-safe callbacks
- Debug mode for troubleshooting
- Region support (COM/EU)
- Lightweight and easy to integrate
To install pandadoc-signing, you can use either npm or yarn:
``bash`
npm install pandadoc-signing
or
`bash`
yarn add pandadoc-signing
First, import the library in your project:
`javascript`
import { Signing } from "pandadoc-signing";
Then, initialize and open the signing experience:
`javascript
const signing = new Signing(
"signing-container",
{},
{
region: "com", // Optional: specify region ('com' or 'eu')
}
);
// Open the signing experience
await signing.open({
sessionId: "your-session-id-here", // Session ID for authentication
});
`
`javascript`
const signing = new Signing(elementId, config, pdConfig);
Parameters:
- elementId (string): The ID of the DOM element to replace with the signing iframeconfig
- (SigningConfig): Signing configuration optionspdConfig
- (PDConfigModel): PandaDoc server configuration
Note: If sessionId is provided in the configuration, the signing experience will automatically open once the iframe is ready and initialized.
Config Options:
`typescript`
interface SigningConfig {
width?: number | string; // Width of the iframe
height?: number | string; // Height of the iframe
sessionId?: string; // Session ID for authentication
debugMode?: boolean; // Enable debug logging
}
PDConfig Options:
`javascript`
const signing = new Signing(
"signing-container",
{
sessionId: "your-session-id-here",
},
{
region: "eu", // Preferred: Use region for better maintainability
host: "https://custom.pandadoc.com/", // Deprecated: Use region instead
}
);
#### open(options)
Opens the signing experience with the specified session ID.
`javascript
await signing.open({
sessionId: "your-session-id-here",
});
// Or, if sessionId was provided in the constructor config:
await signing.open();
`
Parameters:
- options (optional): Partial SigningConfig objectsessionId
- (string, optional): Session ID for authentication. If not provided, uses the sessionId from the constructor configuration.
Returns: Promise
Behavior:
- If no sessionId is provided (neither in options nor in config), the method returns early without opening.sessionId
- If the signing instance is not yet initialized (iframe not ready), the method returns early.
- The method will use the from the constructor config if not provided in the options.
#### close()
Closes the signing experience by dispatching a close command to the iframe.
`javascript`
await signing.close();
Returns: Promise
Behavior:
- If the signing experience is not in a ready state, the method returns early without dispatching the close command.
- When successful, the signing status is set to CLOSED.
#### destroy()
Destroys the signing instance and cleans up resources to prevent memory leaks. This method:
- Destroys the event bus and command bus
- Removes the iframe from the DOM
- Should be called when you no longer need the signing instance
`javascript`
signing.destroy();
Important: After calling destroy(), the signing instance cannot be reused. Create a new instance if needed.
#### on(event, handler)
Registers an event handler for signing session events.
`javascript
signing.on("document.loaded", (payload) => {
console.log("Document loaded:", payload);
});
signing.on("document.completed", (payload) => {
console.log("Document completed:", payload);
});
signing.on("document.exception", (payload) => {
console.error("Document exception:", payload);
});
`
Parameters:
- event (string): Event name. Available events:'document.loaded'
- : Triggered when the document is loaded in the signing session'document.completed'
- : Triggered when a recipient completes the document'document.exception'
- : Triggered if an error occurs during document finalizationhandler
- (function): Callback function that receives the event payload
Returns: The Signing instance (for method chaining)
TypeScript:
`typescript
signing.on("document.loaded", (payload: DocumentEventPayload) => {
// payload is fully typed
});
signing.on("document.completed", (payload: DocumentEventPayload) => {
// payload is fully typed
});
signing.on("document.exception", (payload: DocumentExceptionPayload) => {
// payload is fully typed
});
`
#### off(event, handler?)
Removes an event handler. If no handler is specified, removes all handlers for the event.
`javascript
// Remove specific handler
const handler = (payload) => console.log(payload);
signing.on("document.completed", handler);
signing.off("document.completed", handler);
// Remove all handlers for an event
signing.off("document.completed");
`
Parameters:
- event (string): Event namehandler
- (function, optional): Specific handler to remove
Returns: The Signing instance (for method chaining)
#### once(event, handler)
Registers an event handler that will be called only once.
`javascript`
signing.once("document.completed", (payload) => {
console.log("Document completed (one-time handler):", payload);
});
Parameters:
- event (string): Event namehandler
- (function): Callback function
Returns: The Signing instance (for method chaining)
The library supports event handling through an EventEmitter-style API using .on(), .off(), and .once() methods.
1. 'document.loaded': Triggered when the document is loaded in the signing session.
2. 'document.completed': Triggered when a recipient completes the document.
3. 'document.exception': Triggered if an error occurs during signing.
`javascript
import { Signing } from "pandadoc-signing";
const signing = new Signing(
"signing-container",
{
sessionId: "your-session-id-here",
},
{
region: "com",
}
);
// Register event handlers
signing.on("document.loaded", (payload) => {
console.log("Document loaded:", payload);
// Hide loading spinner, show signing UI, etc.
});
signing.on("document.completed", (payload) => {
console.log("Document completed:", payload);
// Redirect user, update UI, notify backend, etc.
window.location.href = "/thank-you";
});
signing.on("document.exception", (payload) => {
console.error("Document exception:", payload);
// Show error message, log error, etc.
});
await signing.open();
`
#### Multiple Handlers for Same Event
`javascript
// You can register multiple handlers for the same event
signing.on("document.completed", (payload) => {
updateUI(payload);
});
signing.on("document.completed", (payload) => {
notifyBackend(payload);
});
signing.on("document.completed", (payload) => {
trackAnalytics(payload);
});
`
#### One-Time Handlers
`javascript`
// Handler will be called only once, then automatically removed
signing.once("document.completed", (payload) => {
console.log("Document completed:", payload);
});
#### Removing Handlers
`javascript
const handler = (payload) => {
console.log("Document completed:", payload);
};
// Register handler
signing.on("document.completed", handler);
// Later, remove specific handler
signing.off("document.completed", handler);
// Or remove all handlers for an event
signing.off("document.completed");
`
#### Method Chaining
`javascript`
signing
.on("document.loaded", handleLoaded)
.on("document.completed", handleCompleted)
.on("document.exception", handleException);
Enable debug mode to get detailed logging of signing operations:
`javascript`
const signing = new Signing("signing-container", {
sessionId: "your-session-id",
debugMode: true,
});
When debug mode is enabled, you'll see console logs for:
- Constructor initialization
- Open/close operations with parameters
- Command dispatching
- iframe creation
- Event subscription
- Error conditions and their reasons
- Resource cleanup during destroy
Debug logs are prefixed with [Signing Debug] and include relevant data for troubleshooting.
You can specify which PandaDoc region to use by setting the region parameter:
`javascript
// For global region (default)
const signing = new Signing(
"signing-container",
{
sessionId: "your-session-id-here",
},
{
region: "com", // Global region - app.pandadoc.com
}
);
// For EU region
const signing = new Signing(
"signing-container",
{
sessionId: "your-session-id-here",
},
{
region: "eu", // EU region - app.pandadoc.eu
}
);
`
- 'com': Global region (app.pandadoc.com) - Default'eu'
- : EU region (app.pandadoc.eu)
`javascript
import { Signing } from "pandadoc-signing";
// Initialize the signing experience
const signing = new Signing(
"signing-container",
{
sessionId: "your-session-id-here",
debugMode: true, // Enable for development
width: 800,
height: 600,
},
{
region: "com", // Use global region (default)
}
);
try {
// Open the signing experience
await signing.open();
// ... user signs the document ...
// Close when done (optional - usually handled by the signing flow)
await signing.close();
} finally {
// Always clean up resources
signing.destroy();
}
`
`html`
To use this library, you need to obtain an Session ID from the PandaDoc API:
1. Call the Create Document Signing Session API endpoint
2. Extract the id session ID from the responsepandadoc-signing
3. Use the session ID with the library
Example API call:
`javascript
const response = await fetch(
"https://api.pandadoc.com/public/v1/documents/{document_id}/session",
{
method: "POST",
headers: {
Authorization: "API-Key your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
recipient: "recipient@example.com",
lifetime: 3600, // Session lifetime in seconds
}),
}
);
const data = await response.json();
const sessionId = data.id; // This is your session ID
`
For the best user experience, you can initialize the Signing widget before requesting the session ID from the API. This allows the iframe and all static resources to load in advance, so when the session ID is ready, the document opens almost instantly.
Recommended Pattern:
`javascript
import { Signing } from "pandadoc-signing";
// Step 1: Initialize the signing widget immediately (without session ID)
const signing = new Signing(
"signing-container",
{
debugMode: true,
},
{
region: "com",
}
);
// Step 2: Request session ID from your backend in parallel
const sessionPromise = fetch("/api/create-signing-session", {
method: "POST",
body: JSON.stringify({ documentId: "your-document-id" }),
})
.then((response) => response.json())
.then((data) => data.sessionId);
// Step 3: Once session ID is ready, open immediately (resources already loaded!)
const sessionId = await sessionPromise;
await signing.open({ sessionId });
// The document will open almost instantly because the iframe is already initialized
`
Benefits:
- ✅ Static resources (iframe, scripts, styles) load in parallel with your API call
- ✅ Reduces perceived loading time significantly
- ✅ Document opens quickly when session ID is available
- ✅ Better user experience, especially on slower connections
Without Pre-initialization (Slower):
`javascript`
// ❌ Not recommended: Initialize only after getting session ID
const sessionId = await getSessionIdFromAPI();
const signing = new Signing("signing-container"); // Resources start loading NOW
await signing.open({ sessionId }); // User waits for iframe + resources + document
With Pre-initialization (Faster):
`javascript`
// ✅ Recommended: Initialize while fetching session ID
const signing = new Signing("signing-container"); // Resources start loading NOW
const sessionId = await getSessionIdFromAPI(); // Parallel execution
await signing.open({ sessionId }); // Only document loading time, resources ready!
`javascript
const signing = new Signing("signing-container", {
sessionId: "session-id-from-api",
});
await signing.open();
`
`javascript
const signing = new Signing("signing-container", {
sessionId: "session-id-from-api",
width: 1024,
height: 768,
});
await signing.open();
`
`javascript
const signing = new Signing(
"signing-container",
{
sessionId: "session-id-from-api",
},
{
region: "eu",
}
);
await signing.open();
`
`javascript
const signing = new Signing("signing-container", {
sessionId: "session-id-from-api",
debugMode: true,
});
await signing.open();
// Check console for detailed logs
`
`javascript
const signing = new Signing("signing-container", {
sessionId: "session-id-from-api",
});
signing.on("document.loaded", (payload) => {
// Document is ready for signing
hideLoadingSpinner();
});
signing.on("document.completed", (payload) => {
// Handle successful completion
showSuccessMessage();
redirectToThankYouPage();
});
signing.on("document.exception", (payload) => {
// Handle errors
showErrorMessage(payload || "An error occurred");
});
await signing.open();
`
The library supports all modern browsers that support:
- ES2015+
- iframe embedding
- Promises/async-await
To build the library, run:
`bash`
vite build
This will generate the library bundle in the dist directory.
The library includes TypeScript definitions out of the box. Import types as needed:
`typescript
import {
Signing,
SigningConfig,
PDConfigModel,
Region,
DocumentEventPayload,
DocumentExceptionPayload,
} from "pandadoc-signing";
const config: SigningConfig = {
sessionId: "your-session-id-here",
debugMode: true,
width: 800,
height: 600,
};
const pdConfig: PDConfigModel = {
region: Region.COM,
};
const signing = new Signing("signing-container", config, pdConfig);
// Event handlers with full type safety
signing.on("document.loaded", (payload: DocumentEventPayload) => {
console.log("Document loaded");
});
signing.on("document.completed", (payload: DocumentEventPayload) => {
console.log("Document completed:", payload.status);
});
signing.on("document.exception", (payload: DocumentExceptionPayload) => {
console.error("Error:", payload.error);
});
``
- PandaDoc API Documentation
- Create Session Link API
- Embedded Signing Guide