Embed a Clickwrap
This is a simple example outlining how to embed a clickwrap within an iframe.
Goal
Technical audiences will learn how to use Ironclad's Clickwrap Workflow to implement embedded clickwrap in their own pages. Some examples of where this use case/flow could be used:
- Accepting Terms of Service during account creation
- Clicking "I agree" before starting a free trial
- Privacy Policy acknowledgment
- Cookie consent banners
Overview
This guide will cover the steps for embedding a clickwrap in your own page, once you have created and published a Clickwrap Workflow.
Prerequisites
- Clickwrap Workflows must be purchased and enabled for your company.
- A Clickwrap Workflow must be created and published.
- The hostname of the page you are embedding clickwrap within must be listed as an allowed domain in the published Clickwrap Workflow.
Public API access is helpful, but not strictly required to use and embed Clickwrap Workflows.
Note: This guide and any software contained in it should be used at your own risk by individuals qualified to evaluate its effectiveness. IT IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IRONCLAD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH YOUR USE OF THIS GUIDE OR ANY SOFTWARE IT CONTAINS.
Your use of the Ironclad API must comply with Ironclad’s API Terms of Use (available at https://legal.ironcladapp.com/api-terms-of-use) and the terms of the Enterprise Services Agreement (or equivalent) entered into between you and Ironclad.
Details
Data Schema
Just as with launching workflows via the public API, launching a Clickwrap Workflow requires knowledge of the data schema of the workflow configuration, to correctly format the launch payload.
If you have access to the public API, you can use the Retrieve a Workflow Schema endpoint to retrieve the schema for your Clickwrap Workflow.
If you do not have access to the public API, you can check the properties panel in Workflow Designer for data types and property keys for each property. The property key is the same as the property code, except without the enclosing []
brackets (e.g. if [counterpartyName]
is the property code copied from Workflow Designer, counterpartyName
is the property key).

Example values for each data type can be found in the Launch a Workflow guide.
Iframe Element URL
Once you have the minimum requirements, you will need to have a clickwrap workflow created. Before publishing, please make sure that on the Create tab, under Settings, that you have added the domain URL of the page where you will be embedding. Then, you can publish the workflow and click “Copy Link”. The link that is copied to your clipboard is the link you will use for the iframe URL.

More information about configuring a Clickwrap Workflows can be found in our help center.
The URL provided is of the format https://ironcladapp.com/clickwrap/:workflowTemplateId?jwt=...
(e.g. https://ironcladapp.com/clickwrap/683774e21040896675e566f8?jwt=ey12345...
). You will need the workflow template ID to send messages to the iframe.
For your iframe element, there is one special detail parameter that you will also need to add to the URL you just copied. You will need to add a query parameter parent
where the value is the domain of what you entered when configuring your allowed origins. For example, you may add to the end of the copied iframe a value like &parent=https://www.example.com
.
Once you have that full URL (i.e. copied URL + parent
parameter), you can add an iframe element to the page with the full URL as the src
.
Loading Embedded Clickwrap
To load the checkbox and acceptance language, you must post a set_data
message to the iframe. The iframe will not load and display the checkbox and acceptance language without receiving this message.
Here is an example of sending the set_data
message:
const CLICKWRAP_IFRAME_ORIGIN = 'https://ironcladapp.com';
// template ID, which is the ID in the iframe URL
const WORKFLOW_TEMPLATE_ID = '64c069ba71c24ecfff394e3a';
type ParentToIframeMessageData = (
| { type: 'send_acceptance' }
| { type: 'set_data'; data: { formValues: Record<string, any> } }
) & { workflowTemplateId: string };
function sendIframeMessage(messageData: ParentToIframeMessageData) {
const iframeInPage = document.getElementById('my-iframe');
if (iframeInPage == null) {
console.error('Unable to find iframe in page');
return;
}
iframeInPage.contentWindow.postMessage(messageData, CLICKWRAP_IFRAME_ORIGIN);
};
sendIframeMessage({
type: 'set_data',
data: {
// same payload shape as launching a workflow via public API
formValues: {
counterpartyName: 'Example Counterparty',
counterpartySignerEmail: '[email protected]'
}
},
workflowTemplateId: WORKFLOW_TEMPLATE_ID
});
Once the data has been set in the iframe, the checkbox and acceptance language should show, as long as the data and set of default values set in Workflow Designer includes all required properties (signer name and email) and adheres to the schema described in the first section.
Loading Clickwrap Right Away
To get the acceptance language and checkbox in the iframe to display right away when you have not yet collected all the data for the acceptance, you can set default data values in the iframe as soon as the page loads — so immediately after receiving the clickwrap_initialized
event — instead of waiting for the user to populate all the form values. This can also be done by posting a subset of form values — or even an empty object as form values — if default values are set for all form values in WFD.
function handleMessage(message: MessageEvent) {
if (message.origin !== CLICKWRAP_IFRAME_ORIGIN) {
return;
}
if (message.data.type === "clickwrap_initialized") {
sendIframeMessage({
type: "set_data",
data: {
formValues: {}, // use default values from Workflow Designer
},
workflowTemplateId: WORKFLOW_TEMPLATE_ID,
});
}
}
However, you should still send an updated set_data
message with the final values before sending the send_acceptance
message to capture acceptance, to ensure that the correct metadata is included with the acceptance.
Sending Acceptance
Similar to how you set data above, you will also need to send the iframe a message to trigger an acceptance.
sendIframeMessage({
type: "send_acceptance",
workflowTemplateId: WORKFLOW_TEMPLATE_ID,
});
As a prerequisite, the checkbox must be checked to send the acceptance. If data has not yet been set in the clickwrap iframe, the clickwrap has not loaded, or the checkbox is not checked, the acceptance will not send, and the parent page will receive a clickwrap_acceptance_failed
message from the iframe.
Listening For Events
In turn, the embedded clicwkrap page also sends events to the parent page.
There are various events that you can listen to using an event listener. Once you have attached an event listener to the iframe, you may see the following events:
type IframeToParentMessageData =
// Triggered when the application has loaded and is ready to accept events (e.g. `set_data`).
| { type: 'clickwrap_initialized' }
// Triggered when the application receives the `set_data` calls and is in the process of retrieving additional data.
| { type: 'clickwrap_is_loading' }
// Triggered when the application successfully retrieved data after `set_data` was called.
// This is typically the time where the checkbox and acceptance language will now show.
| { type: 'clickwrap_load_succeeded' }
// Triggered when the application was not able to retrieve data after `set_data` was called.
| { type: 'clickwrap_load_failed', data: { err: unknown } }
// Triggered when a user clicks on a link in the acceptance language.
// The template is the ID of the template clicked, and the URL is the link used to download the document.
| { type: 'clickwrap_link_clicked', data: { template: string; url: string } }
// Triggered when the user checks the checkbox.
// This is often useful for when you want to make other actions available (e.g. submit button becomes clickable).
| { type: 'clickwrap_checkbox_checked' }
// Triggered when the user unchecks the checkbox.
// This is often useful if you need to prevent the user from taking a future action until the box is checked again.
| { type: 'clickwrap_checkbox_unchecked' }
// Triggered when the `send_acceptance` event successfully sends acceptance.
| { type: 'clickwrap_acceptance_succeeded' }
// Triggered when the `send_acceptance` event was unable to successfully send acceptance.
| { type: 'clickwrap_acceptance_failed', data: { err: unknown } };
Example Skeleton Listener
function handleMessage(message: MessageEvent) {
if (message.origin !== CLICKWRAP_IFRAME_ORIGIN) {
return;
}
switch (message.data.type) {
case 'clickwrap_initialized':
// loaded, can now set data
case 'clickwrap_is_loading':
// data has been set, clickwrap is loading within iframe
case 'clickwrap_load_succeeded':
// clickwrap is loaded and is now interactable
case 'clickwrap_load_failed':
// clickwrap loading failed, check data and try setting data again
case 'clickwrap_link_clicked':
// notification that the user has downloaded a clickwrap document
case 'clickwrap_checkbox_checked':
// The user has checked the checkbox for acceptance.
// If this is a required part of your user flow, other dependent actions,
// e.g. a form submit button, can now be enabled.
// Update your app state accordingly.
// It is now permissable to post the `send_acceptance` event to the iframe.
case 'clickwrap_checkbox_unchecked':
// The user has previously checked the checkbox for acceptance,
// but has now unchecked the box.
// If this is a required part of your user flow, other dependent actions,
// e.g. a form submit button, can now be disabled.
// Update your app state accordingly.
case 'clickwrap_acceptance_succceeded':
// The acceptance has been successfully sent and recorded.
case 'clickwrap_acceptance_failed':
// The acceptance was not successfully sent and recorded.
default:
console.error('invalid message', { message });
}
}
const iframe = document.getElementById('my-iframe');
iframe.addEventListener("load", () => {
window.addEventListener("message", handleMessage);
});
Updated 2 days ago