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:

  1. Create a document pack using the QuicklySign API.
  2. Include generate_view_url=true in the document-pack request.
  3. Set host_url to the origin of the application containing the embedded page.
  4. Retrieve the viewing_link from the API response.
  5. Load the QuicklySign JavaScript widget.
  6. Pass the viewing_link to QuicklySign.open().
  7. Wait for the doc_ready event.
  8. 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_packs

Include the following query parameters:

ParameterDescription
generate_view_urlSet to true to generate a setup viewing link.
host_urlThe 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.com

The 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.com

Do not include the page path.

Correct:

https://documents.example.com

Incorrect:

https://documents.example.com/document/setup

The 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.com

the embedded setup page can be hosted at:

https://documents.example.com/document/setup

The origin is still:

https://documents.example.com

Example 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-1

must 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_link

Example:

{
  "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

PropertyDescription
name_visibleDetermines whether the document-pack name is visible.
signatory_details_visibleDetermines whether signatory setup is visible.
settings_visibleDetermines whether document settings are visible.
user_defined_attributes_visibleDetermines whether user-defined attributes are visible.
tabs_visibleDetermines whether the document tabs are visible.
upload_new_document_enabledDetermines 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_ready

The 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_ready event has been received
  • The widget exposes the update_status method
  • 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_success

The 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_configuration to 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:

  1. Confirm the exact origin used by the parent application.
  2. Supply that origin as host_url.
  3. Generate a new viewing link.
  4. 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=true was included
  • host_url was 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-ancestors

Confirm 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_ready was received
  • The object returned by QuicklySign.open() exists
  • setupWidget.update_status is 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_success

Disable the Request signatures button and show a confirmation message.