Using In-app Signing

How In-App Signing Works

In-app signing allows your application to host the signing experience directly within your own interface.

Your application embeds the signing page using an iframe, allowing signers to complete the signing process without leaving your platform.

The typical workflow is:

  1. Create a document pack
  2. Retrieve the signatory_uuid and document_pack_key.
  3. Generate an in-app signing URL
  4. Render the signing page inside an iframe

Step 1: Create a Document Pack

Create the document pack and define the signatories who will sign the document.

Example request body:

{
  "document_pack_name": "My Document Pack",
  "documents": [
    {
      "document_name": "My Form",
      "document_creation_settings": {
        "base_64_document": "<base64_content>",
        ...
      }
    }
  ],
  "signatories": [
    {
      "name": "Signer 1",
      "email": "[email protected]",
      "role": "signer-1"
    }
  ]
}

Tip: Always store the document_pack.key returned in the response. You will need it when generating the signing URL.


Step 2: Retrieve the Signatory UUID

Each signer has a unique signatory_uuid.

This value is returned when the document pack is created and can be accessed from:

data.document_pack.signatories[n].uuid

Each signer must have their own signing URL, so a signing URL must be generated separately for each signatory_uuid.


Step 3: Generate the In-App Signing URL

Generate a signing URL for the signatory using the following endpoint:

GET /v1/document_packs/{{document_pack_key}}/generate_in_app_signing_url/{{signatory_uuid}}?signing_page_url={{signing_page_url}}

Query Parameters

ParameterDescription
signer_is_validated (optional)Default: false. If false, the signer must complete OTP verification after signing. Setting this to true disables validation and requires pre-approval. Please contact [email protected] for more information.
signing_page_urlThe URL where the signing iframe will be hosted. Must be URL-encoded. If not provided here, it must be defined in the document as in_app_page_url.
is_in_person (optional)If true, the signing interface requires confirmation that the document was signed in each other's presence.

Important Notes

Domain Restrictions

Signing URLs are restricted to the domain specified in signing_page_url.

Using localhost directly will not work because the signing service must be able to access the domain.

For local testing, you may use a publicly accessible tunnel such as ngrok.


URL Encoding

The signing_page_url must be URL-encoded.

Example:

https://app.example.com/sign

Becomes:

https%3A%2F%2Fapp.example.com%2Fsign

Avoid double-encoding the URL, as this will break the signing link.


Signatory UUID Mismatch

If the signing page fails to load correctly, verify that you are using the correct signatory_uuid for the intended signer.

Each signer requires their own unique signing URL.


Step 4: Render the Signing Page

Use the returned signing URL in your application.

QuicklySign provides a JavaScript widget that automatically creates and manages the iframe.

<script src="https://app.quicklysign.com/public/widgets/embed.widget.latest.min.js"></script>

<div id="container"></div>

<script>
QuicklySign.init("<your client id>");

QuicklySign.open({
    url: "<your signing url>",
    container_id: "container",
    message_listener: function(event_data){
        if(event_data.event === QuicklySign.EVENTS.SIGNED){
            alert("document signed");
        }
    },
    post_sign_url: ""
});
</script>

If container_id is not specified, the widget will automatically append an iframe to the <body>.


Parameters for QuicklySign.open()

ParameterDescription
url (optional)Signing URL to load. If not provided, the widget checks for a qs_url query parameter in the host page URL.
container_id (optional)ID of the element (div) where the iframe should be inserted.
message_listener (optional)Callback function triggered when signing is completed or deferred.
post_sign_url (optional)URL to redirect to after signing completes. Use either post_sign_url or message_listener, not both.

Message Listener Events

The message_listener function receives an event object with an event property.

EventDescription
QuicklySign.EVENTS.SIGNEDThe signer has completed signing.
QuicklySign.EVENTS.SIGN_LATERThe signer chose to sign later.

FAQ

Why am I getting a blank signing page?

This usually occurs when signing_page_url is:

  • missing
  • not URL-encoded
  • different from the domain hosting the iframe

Ensure the URL is correctly encoded and matches the domain hosting the iframe.


Do signers still receive emails when using in-app signing?

Yes. By default, signers still receive notification emails and the final signed document.

To fully control the signing workflow and notifications, set:

"can_receive_signing_notifications": false

In this case your application is responsible for sending signing invitations. QuicklySign will still send the final signed document once the process is complete.


Can a single signing URL be used for multiple signers?

No.

Each signer requires a unique signing URL generated using their signatory_uuid.


Why do I receive SIGN_LATER events?

This occurs when a signer chooses to defer signing.

Your application should handle this event and decide how to notify the signer later.


How do I handle multiple signers in sequence?

In-app signing does not automatically move to the next signer.

Your application must manage the workflow:

  1. Receive the user_signed webhook
  2. Identify the next signer
  3. Notify the signer
  4. Generate their signing URL when they access the signing page

Tip: Generate the signing URL just-in-time when the signer arrives to ensure OTP validation works correctly.


Why don’t signature fields open inside the iframe?

Signature and initial fields rely on modal components provided by the QuicklySign widget.

If you embed the signing URL manually without the widget:

  • Signature fields will not open
  • Initial fields will not open

Always include the widget:

<script src="https://app.quicklysign.com/public/widgets/embed.widget.latest.min.js"></script>