Using Embedded Document Setup
Overview
Embedded document setup allows users to prepare and configure a QuicklySign document without leaving your application.
The QuicklySign setup interface is loaded inside your page using the QuicklySign JavaScript widget and an API-generated viewing_link.
Users can:
- View the document
- Add and manage signatories
- Place fields
- Change document settings
- Switch between documents in a document pack
- Add additional documents
- Request signatures
The parent application provides workflow controls, such as the Request signatures button, and communicates with the embedded setup page through the object returned by QuicklySign.open().
How it works
The embedded setup flow consists of the following steps:
- Create a document pack using the QuicklySign API.
- Include
generate_view_url=truein the document-pack request. - Set
host_urlto the origin of the application containing the embedded page. - Retrieve the
viewing_linkfrom the API response. - Load the QuicklySign JavaScript widget.
- Pass the
viewing_linktoQuicklySign.open(). - Wait for the
doc_readyevent. - Use the returned widget object to perform actions such as requesting signatures.
Step 1: Create a Document Pack
Create the document pack using:
POST /v1/document_packsInclude the following query parameters:
| Parameter | Description |
|---|---|
generate_view_url | Set to true to generate a setup viewing link. |
host_url | The origin of the parent application that will contain the embedded setup page. |
Example:
POST /v1/document_packs?generate_view_url=true&host_url=https%3A%2F%2Fdocuments.example.comThe host_url must be URL encoded.
Host URL
The host_url must contain the origin of the application that will host the QuicklySign widget.
An origin contains the:
- Protocol
- Hostname
- Port, where applicable
Example:
https://documents.example.comDo not include the page path.
Correct:
https://documents.example.comIncorrect:
https://documents.example.com/document/setupThe page containing the widget must be opened from the same origin.
For example, when the viewing link is generated using:
host_url=https://documents.example.comthe embedded setup page can be hosted at:
https://documents.example.com/document/setupThe origin is still:
https://documents.example.comExample request body
The document pack can be created from a base64 document:
{
"document_pack_name": "Client Agreement",
"documents": [
{
"document_name": "Agreement",
"document_creation_settings": {
"base_64_document": "<base64_document>",
"extract_fields_by_tag": true
}
}
],
"signatories": [
{
"name": "Signer 1",
"email": "[email protected]",
"role": "signer-1"
}
]
}The document pack can also be created from an existing template:
{
"document_pack_name": "Client Agreement",
"documents": [
{
"document_name": "Agreement",
"document_creation_settings": {
"create_from_template_key": "<template_key>"
}
}
],
"signatories": [
{
"name": "Signer 1",
"email": "[email protected]",
"role": "signer-1"
}
]
}The signatory roles must match the roles assigned to the document fields.
For example, fields assigned to:
signer-1must have a corresponding signatory with:
{
"role": "signer-1"
}Signatories can also be added from the embedded setup interface before signatures are requested.
Step 2: Retrieve the Viewing Link
When the document pack is created with generate_view_url=true, the response contains a viewing_link.
The link is returned at:
data.viewing_linkExample:
{
"data": {
"document_pack": {
"key": "<document_pack_key>"
},
"viewing_link": "https://sandbox.quicklysign.com/view?..."
}
}Use the complete viewing_link returned by the API.
Do not:
- Construct the viewing link manually
- Remove its query parameters
- Extract the temporary token
- Replace it with a signing link
- Pass it through the standalone setup route
- Log the complete link
The viewing link contains temporary access information and should be treated as a sensitive value.
Step 3: Pass the Viewing Link to the Frontend
Create the document pack and retrieve the viewing link from your backend.
Pass the following values to the page containing the embedded setup interface:
- The
viewing_link - The QuicklySign widget client ID
Use JSON-safe serialization when inserting server-generated values into JavaScript.
Example using Jinja:
<script>
const setupUrl = {{ viewing_link | tojson }};
const clientId = {{ client_id | tojson }};
</script>Avoid normal string interpolation:
<script>
const setupUrl = "{{ viewing_link }}";
</script>The viewing link can contain encoded characters and query parameters that must remain unchanged.
Step 4: Load the QuicklySign Widget
Add the QuicklySign widget script to the page:
<script src="https://app.quicklysign.com/public/widgets/embed.widget.latest.min.js"></script>Use the widget script provided for your QuicklySign environment.
Add a container for the embedded setup page:
<div
id="quicklysign-setup"
style="width: 100%; height: 1200px;">
</div>The container must have a height so that the embedded page is visible.
Step 5: Open the Embedded Setup Page
Initialise the widget using your QuicklySign client ID:
QuicklySign.init(clientId);Pass the raw viewing_link to QuicklySign.open():
const setupWidget = QuicklySign.open({
url: setupUrl,
container_id: "quicklysign-setup"
});The widget creates and manages the iframe inside the specified container.
The object returned by QuicklySign.open() is used to communicate with the embedded setup page.
Step 6: Configure the Setup Interface
Use ui_configuration to control which parts of the embedded setup interface are visible.
const setupWidget = QuicklySign.open({
url: setupUrl,
container_id: "quicklysign-setup",
ui_configuration: {
name_visible: true,
signatory_details_visible: true,
settings_visible: true,
user_defined_attributes_visible: true,
tabs_visible: true,
upload_new_document_enabled: false
}
});UI configuration properties
| Property | Description |
|---|---|
name_visible | Determines whether the document-pack name is visible. |
signatory_details_visible | Determines whether signatory setup is visible. |
settings_visible | Determines whether document settings are visible. |
user_defined_attributes_visible | Determines whether user-defined attributes are visible. |
tabs_visible | Determines whether the document tabs are visible. |
upload_new_document_enabled | Determines whether users can add another document from the setup page. |
These properties control the content displayed inside the embedded setup interface.
Workflow actions, such as Request signatures, are added to the parent application.
Step 7: Wait for the Document to Load
Use message_listener to receive events from the embedded setup page.
let setupIsReady = false;
const setupWidget = QuicklySign.open({
url: setupUrl,
container_id: "quicklysign-setup",
message_listener: function (eventData) {
if (eventData.event === "doc_ready") {
setupIsReady = true;
}
}
});The setup page sends the following event when the document is ready:
doc_readyThe event contains:
{
"event": "doc_ready",
"document_pack_key": "<document_pack_key>",
"is_document_pack": true,
"timestamp": "<timestamp>"
}Wait for doc_ready before enabling controls that communicate with the embedded setup page.
The return value from QuicklySign.open() confirms that the widget was created. The doc_ready event confirms that the document setup page is ready.
Step 8: Request Signatures
The parent application must provide its own Request signatures button.
Add the button outside the widget container:
<button id="request-signatures" type="button" disabled>
Request signatures
</button>Enable the button after:
- The
doc_readyevent has been received - The widget exposes the
update_statusmethod - No status update is currently in progress
- The document pack has not already been sent for signatures
To request signatures, call:
setupWidget.update_status("awaiting_signatures");This changes the document pack from setup to awaiting signatures.
Example
<button id="request-signatures" type="button" disabled>
Request signatures
</button>
<div
id="quicklysign-setup"
style="width: 100%; height: 1200px;">
</div>
<script src="https://app.quicklysign.com/public/widgets/embed.widget.latest.min.js"></script>
<script>
const setupUrl = "<viewing_link>";
const clientId = "<client_id>";
const requestSignaturesButton =
document.getElementById("request-signatures");
let setupWidget = null;
let setupIsReady = false;
let updateInProgress = false;
let updateSucceeded = false;
function refreshRequestSignaturesButton() {
const updateMethodAvailable =
setupWidget &&
typeof setupWidget.update_status === "function";
requestSignaturesButton.disabled =
!setupIsReady ||
!updateMethodAvailable ||
updateInProgress ||
updateSucceeded;
}
QuicklySign.init(clientId);
setupWidget = QuicklySign.open({
url: setupUrl,
container_id: "quicklysign-setup",
message_listener: function (eventData) {
if (eventData.event === "doc_ready") {
setupIsReady = true;
refreshRequestSignaturesButton();
}
},
on_update_success_listener: function () {
updateInProgress = false;
updateSucceeded = true;
refreshRequestSignaturesButton();
},
on_update_failure_listener: function () {
updateInProgress = false;
updateSucceeded = false;
refreshRequestSignaturesButton();
},
ui_configuration: {
name_visible: true,
signatory_details_visible: true,
settings_visible: true,
user_defined_attributes_visible: true,
tabs_visible: true,
upload_new_document_enabled: false
}
});
refreshRequestSignaturesButton();
requestSignaturesButton.addEventListener(
"click",
function () {
if (
!setupIsReady ||
updateInProgress ||
updateSucceeded ||
!setupWidget ||
typeof setupWidget.update_status !== "function"
) {
return;
}
updateInProgress = true;
refreshRequestSignaturesButton();
try {
setupWidget.update_status("awaiting_signatures");
} catch (error) {
updateInProgress = false;
refreshRequestSignaturesButton();
console.error(
"The document status update could not be started.",
error
);
}
}
);
</script>Status Update Callbacks
Use the update callbacks to determine whether the status change succeeded or failed.
Successful update
on_update_success_listener: function (eventData) {
console.log("Document status updated successfully.");
}A successful status update sends:
update_status_successThe event contains:
{
"event": "update_status_success",
"document_pack_key": "<document_pack_key>"
}After a successful update:
- Set the update state to complete
- Keep the Request signatures button disabled
- Show a confirmation message to the user
Failed update
on_update_failure_listener: function (eventData) {
console.error("The document status could not be updated.");
}After a failed update:
- Clear the in-progress state
- Re-enable the Request signatures button
- Display the returned validation message to the user
A status update can fail when the document pack is not ready to be sent.
Before requesting signatures, ensure that:
- The required signatories have been added
- Signatory roles match the assigned fields
- Required fields have been configured
- The document pack is still in setup
Preventing Repeated Requests
The parent application must prevent repeated calls to:
setupWidget.update_status("awaiting_signatures");Disable the Request signatures button:
- Before
doc_ready - While the status update is in progress
- After the status update succeeds
Re-enable the button only when the status update fails and the document pack remains in setup.
Complete Example
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Embedded Document Setup</title>
<script src="https://app.quicklysign.com/public/widgets/embed.widget.latest.min.js"></script>
</head>
<body>
<h1>Document Setup</h1>
<p id="setup-status" aria-live="polite">
Loading document setup...
</p>
<button id="request-signatures" type="button" disabled>
Request signatures
</button>
<div
id="quicklysign-setup"
style="width: 100%; height: 1200px;">
</div>
<script>
const setupUrl = "<viewing_link>";
const clientId = "<client_id>";
const statusElement =
document.getElementById("setup-status");
const requestSignaturesButton =
document.getElementById("request-signatures");
let setupWidget = null;
let setupIsReady = false;
let updateInProgress = false;
let updateSucceeded = false;
function setStatus(message) {
statusElement.textContent = message;
}
function updateMethodAvailable() {
return Boolean(
setupWidget &&
typeof setupWidget.update_status === "function"
);
}
function refreshRequestSignaturesButton() {
requestSignaturesButton.disabled =
!setupIsReady ||
!updateMethodAvailable() ||
updateInProgress ||
updateSucceeded;
}
try {
QuicklySign.init(clientId);
setupWidget = QuicklySign.open({
url: setupUrl,
container_id: "quicklysign-setup",
message_listener: function (eventData) {
if (!eventData || !eventData.event) {
return;
}
if (eventData.event === "doc_ready") {
setupIsReady = true;
setStatus("Document setup is ready.");
refreshRequestSignaturesButton();
}
if (eventData.event === "update_status_success") {
updateInProgress = false;
updateSucceeded = true;
setStatus("Signatures have been requested.");
refreshRequestSignaturesButton();
}
},
on_update_success_listener: function () {
updateInProgress = false;
updateSucceeded = true;
setStatus("Signatures have been requested.");
refreshRequestSignaturesButton();
},
on_update_failure_listener: function () {
updateInProgress = false;
updateSucceeded = false;
setStatus(
"The document could not be sent for signatures."
);
refreshRequestSignaturesButton();
},
ui_configuration: {
name_visible: true,
signatory_details_visible: true,
settings_visible: true,
user_defined_attributes_visible: true,
tabs_visible: true,
upload_new_document_enabled: false
}
});
refreshRequestSignaturesButton();
} catch (error) {
setStatus(
"The embedded setup page could not be initialised."
);
console.error(error);
}
requestSignaturesButton.addEventListener(
"click",
function () {
if (
!setupIsReady ||
!updateMethodAvailable() ||
updateInProgress ||
updateSucceeded
) {
return;
}
updateInProgress = true;
setStatus("Requesting signatures...");
refreshRequestSignaturesButton();
try {
setupWidget.update_status("awaiting_signatures");
} catch (error) {
updateInProgress = false;
updateSucceeded = false;
setStatus(
"The signature request could not be started."
);
refreshRequestSignaturesButton();
console.error(error);
}
}
);
</script>
</body>
</html>Adding Signatories
Signatories can be added:
- When the document pack is created
- Through the embedded setup interface
- From the parent application using the widget
To add a signatory from the parent application, use:
setupWidget.add_signatory(
"Signer name",
"[email protected]",
"+27000000000"
);Ensure that the signatory role and assigned document fields are configured before requesting signatures.
Updating the UI Configuration
The parent application can change the embedded setup configuration after the widget has loaded.
Use:
setupWidget.update_ui_configuration({
name_visible: true,
signatory_details_visible: false,
settings_visible: true,
user_defined_attributes_visible: false,
tabs_visible: true,
upload_new_document_enabled: false
});This allows the parent application to show or hide setup sections without reloading the embedded page.
Other Widget Actions
Revert the document pack to setup
setupWidget.update_status("setup");Switch document tabs
setupWidget.switch_tab();These actions must be called through the object returned by QuicklySign.open().
Do not access or modify the iframe contents directly.
Embedded Setup and Standalone Setup
Embedded setup and standalone setup are separate integration methods.
Embedded setup
Embedded setup:
- Uses the raw API-generated
viewing_link - Loads through
QuicklySign.open() - Displays inside the parent application
- Uses
ui_configurationto control setup sections - Uses parent-application controls for actions such as Request signatures
Standalone setup
Standalone setup:
- Opens in a separate browser window or tab
- Uses the standalone setup route
- Provides its own action controls
- Is opened using
window.open()
Do not pass the standalone setup page to QuicklySign.open().
Use the raw viewing_link for embedded setup.
Domain Restrictions
The embedded setup page is restricted to the origin supplied in host_url.
If the parent page is opened from another origin, the browser blocks the embedded page.
The browser console may show:
Content-Security-Policy:
The page's settings blocked the loading of a resource
because it violates the frame-ancestors directive.To resolve this:
- Confirm the exact origin used by the parent application.
- Supply that origin as
host_url. - Generate a new viewing link.
- Open the parent page through the same origin.
Changing host_url does not update a viewing link that has already been generated.
Security Considerations
The viewing link contains temporary access information.
Do not:
- Log the complete viewing link
- Store it in browser local storage
- Send it to analytics services
- Include it in error-tracking metadata
- Display its query parameters
- Share it with unauthorised users
The API access token must remain on the server.
Do not include the API access token in frontend JavaScript.
Widget events can include document identifiers. Avoid logging complete event objects in production.
Troubleshooting
The embedded setup page does not load
Confirm that:
generate_view_url=truewas includedhost_urlwas supplied- The API returned
data.viewing_link - The raw viewing link was passed to
QuicklySign.open() - The widget script loaded
- The client ID belongs to the same environment
- The widget container has a height
The iframe is blocked
Check the browser console for:
frame-ancestorsConfirm that the parent origin matches the host_url used when the viewing link was generated.
Generate a new viewing link after correcting the origin.
The Request signatures button remains disabled
Confirm that:
doc_readywas received- The object returned by
QuicklySign.open()exists setupWidget.update_statusis available- No update is currently in progress
- A previous update has not already succeeded
Request signatures fails
Confirm that:
- The required signatories have been added
- Signatory roles match the assigned fields
- Required fields are configured
- The document pack is still in setup
- The update failure callback is handled
The status update succeeds
Handle:
update_status_successDisable the Request signatures button and show a confirmation message.
Updated 17 days ago
