Clickwrap SDK
The Clickwrap product and its JavaScript SDK let you capture recorded acceptance of one or more legal agreements before a user continues — common patterns include checkout, account creation, beta enrolment, or access to a gated feature. The SDK renders checkboxes, links, or a full agreement viewer inside your page or modal, then submits acceptance to SpotDraft so you have an auditable contract record linked to your user identifier.
Choose the integration path that matches your platform:
| Implementation | Best for | Guide |
|---|---|---|
Web (npm, recommended) | React, Next.js, Angular, Vue, and other bundled apps | This page |
Web (CDN) | Plain HTML pages or no-build-step setups | This page — CDN section |
| Mobile (iOS / Android) | Native iOS or Android apps | Mobile Integration |
After execution, legal teams review activity in the Clickwrap area of SpotDraft. Branding, supported domains, and other options are under Clickwrap settings.
Naming note: This guide uses Clickwrap for the product and for integrator-facing names in
examples — such as clickwrapId, a clickwrap instance variable, and host element ids like
clickwrap-host. The current @spotdraft/clickwrap-client export is still SdClickthrough
(for example window.SdClickthrough, sdClickthroughLoaded, and sd-clickthrough-* CSS classes)
so existing integrations keep working without changes. An upcoming npm release will introduce
SdClickwrap as the preferred export and deprecate SdClickthrough; we will publish a
migration path before the legacy name is removed.
Quick Setup
Follow the five steps below to go from zero to a working integration.
1. Install the SDK
Add the package to your project with npm (or yarn / pnpm). For plain HTML pages without a build step, use the CDN script instead.
- npm
- CDN
npm install @spotdraft/clickwrap-client
<script
type="module"
src="https://sdk.spotdraft.com/clickwrap/v1/sdk.js"
></script>
2. Copy your values from SpotDraft
In the SpotDraft Clickwrap console, open View snippet for your Clickwrap packet and copy:
clickwrapId— identifies your Clickwrap packetbaseUrl— the region-specific API base URL for your workspace
See Regional baseUrl if you need to verify
the correct host for your cluster.
// Values are available under View snippet in the
// SpotDraft Clickwrap console.
const clickwrapId = "YOUR_CLICKWRAP_ID";
const baseUrl = "https://api.in.spotdraft.com/api/";
// ^^^ replace with your region's URL
3. Add a host element
Add an empty <div> to the page or component where you want the
Clickwrap UI to render. The SDK mounts inside this element.
Pair it with a submit button that stays disabled until the user has accepted all required agreements.
<div id="clickwrap-host"></div>
<button id="submit-btn" disabled>Submit</button>
4. Initialise the SDK and handle acceptance
Construct SdClickthrough with your clickwrapId,
hostLocationDomId, and baseUrl, then call
await clickwrap.init() to mount the agreement UI. Use the
acceptanceToggled event to keep the submit button state in sync
with the user’s consent state.
- TypeScript (npm)
- JavaScript (CDN)
import { SdClickthrough } from "@spotdraft/clickwrap-client";
const submitButton = document.getElementById("submit-btn")!;
const clickwrap = new SdClickthrough({
clickwrapId: "CLICKWRAP_ID_FROM_CONSOLE",
hostLocationDomId: "clickwrap-host",
baseUrl: "BASE_URL_FROM_CONSOLE",
});
await clickwrap.init();
clickwrap.on("acceptanceToggled", (isAccepted: boolean) => {
submitButton.disabled = !isAccepted;
});
function initClickwrap() {
const clickwrap = new window.SdClickthrough({
clickwrapId: "CLICKWRAP_ID_FROM_CONSOLE",
hostLocationDomId: "clickwrap-host",
baseUrl: "BASE_URL_FROM_CONSOLE",
});
clickwrap.init();
clickwrap.on("acceptanceToggled", (isAccepted) => {
document.getElementById("submit-btn").disabled = !isAccepted;
});
}
if (window.SdClickthrough) {
initClickwrap();
} else {
window.addEventListener("sdClickthroughLoaded",
initClickwrap, { once: true });
}
5. Submit acceptance
When the user clicks the submit button, call clickwrap.submit()
with a stable user_identifier. The method resolves with the created
clickwrap contract data. Proceed with your signup or checkout flow after it resolves.
submitButton.addEventListener("click", async () => {
const contract = await clickwrap.submit({
user_identifier: "user@example.com",
});
console.log("Clickwrap contract created:", contract);
// ✅ Proceed with your signup / checkout flow here
});
That's it. When the integration is working:
- The agreement UI renders inside
clickwrap-host. - The Submit button stays disabled until all required agreements are accepted.
- Clicking Submit records consent successfully and
submit()resolves with the created clickwrap contract data.
Prerequisites
Before integrating, confirm the following in the SpotDraft Clickwrap console:
- Active Clickwrap workspace — You need a SpotDraft account with Clickwrap enabled. If you are evaluating the product, request a demo. Inside that account, create at least one Clickwrap object: it groups the contract templates end users must accept and controls branding and snippet values.
- Contracts attached to the Clickwrap — Add every legal document that makes up the package users must accept. Without published contracts the SDK has nothing to render.
- Supported domains — Under Clickwrap settings, add every origin that will load the SDK so the
Referersent by the browser is allowed. If the domain is missing you may see "Request received from invalid domain".localhostis allowlisted by default for local testing. - HTTPS in production — Serve integration pages over HTTPS in staging and production. Local
http://localhostis supported for development.
Default agreement URLs: Published agreements are often served on SpotDraft-hosted URLs such as https://clickwrap.<region>.spotdraft.com/<workspace_id>/.... To use your own hostname, configure domain / host mapping under Legal Hub Pages and complete DNS verification. Guide: Custom domain for Clickwrap.
Tip: Most IDs, baseUrl, and copy-pastable snippets are available from the Clickwrap page in the product. Use View snippet to avoid transcription errors.
Installation
- NPM (recommended)
- CDN
npm install @spotdraft/clickwrap-client
ES modules (React, Angular, Vue, etc.):
import {
SdClickthrough,
SdClickthroughEvents,
} from "@spotdraft/clickwrap-client";
CommonJS:
const { SdClickthrough } = require("@spotdraft/clickwrap-client");
Add the module script to your page (preferably in <head>) so the SDK loads before your code runs:
<script
type="module"
src="https://sdk.spotdraft.com/clickwrap/v1/sdk.js"
></script>
SdClickthrough is exposed on window after load. Always guard initialization with the sdClickthroughLoaded event:
function initializeClickwrap() {
const clickwrap = new window.SdClickthrough({
clickwrapId: "CLICKWRAP_ID_FROM_CONSOLE",
hostLocationDomId: "HOST_ELEMENT_DOM_ID",
baseUrl: "BASE_URL_FROM_CONSOLE",
});
clickwrap.init();
}
if (window.SdClickthrough) {
initializeClickwrap();
} else {
window.addEventListener("sdClickthroughLoaded",
initializeClickwrap, { once: true });
}
Initialization
- Instantiate
new SdClickthrough({ ... })with at least the three required fields below. - Call
await init()(NPM) orinit()(CDN) so the SDK can mount into the DOM for inline layouts.modallayout often mounts nothing until you callopenConsentDialog().
| Field | Purpose |
|---|---|
clickwrapId | Public identifier of the Clickwrap shown on the Clickwrap settings page |
hostLocationDomId | The id of a DOM node in your page where the Clickwrap UI should render |
baseUrl | API base URL for this Clickwrap, provided next to View snippet in the console |
NPM:
import { SdClickthrough } from "@spotdraft/clickwrap-client";
const clickwrap = new SdClickthrough({
clickwrapId: "CLICKWRAP_ID_FROM_CONSOLE",
hostLocationDomId: "HOST_ELEMENT_DOM_ID",
baseUrl: "BASE_URL_FROM_CONSOLE",
});
await clickwrap.init();
CDN / browser script — initialize immediately if window.SdClickthrough is already available; otherwise wait for sdClickthroughLoaded:
function initializeClickwrap() {
const SdClickthrough = window.SdClickthrough;
const clickwrap = new SdClickthrough({
clickwrapId: "CLICKWRAP_ID_FROM_CONSOLE",
hostLocationDomId: "HOST_ELEMENT_DOM_ID",
baseUrl: "BASE_URL_FROM_CONSOLE",
});
clickwrap.init();
}
if (window.SdClickthrough) {
initializeClickwrap();
} else {
window.addEventListener("sdClickthroughLoaded", initializeClickwrap, {
once: true,
});
}
Difference: NPM can call init immediately after import. For the browser script path, initialize right away if window.SdClickthrough already exists; otherwise wait for sdClickthroughLoaded.
Next, pick the layout that best matches your UI. See Examples for detailed implementations of:
- inline checkbox list using
LayoutMode.EMBEDDED_DEFAULT - agreement viewer using
LayoutMode.EMBEDDED_AGREEMENT_VIEWER - modal dialog using
LayoutMode.MODAL
Advanced: displayConfig
Pass displayConfig to control layout mode, labels, theming, and modal
behaviour. All fields are optional — the defaults work for most integrations.
For complete layoutMode implementations, see Examples.
displayConfig properties
| Property | Type | Default | Description |
|---|---|---|---|
layoutMode | string | LayoutMode | 'embedded-default' | 'embedded-default', 'embedded-agreement-viewer', or 'modal' |
title | string | — | Header title (viewer / modal) |
subtitle | string | — | Header subtitle (viewer / modal) |
tabOrientation | string | 'horizontal' | 'horizontal', 'vertical-left', 'vertical-right' |
behavior.allowCloseOnBackdrop | boolean | false | Let modal close on backdrop click |
behavior.allowCloseOnEscape | boolean | false | Let modal close on Escape key |
labels.acceptButton | string | "Accept and continue" | Override the primary CTA label |
labels.cancelButton | string | "Cancel" | Override the secondary CTA label |
import { SdClickthrough, LayoutMode } from "@spotdraft/clickwrap-client";
const clickwrap = new SdClickthrough({
clickwrapId: "CLICKWRAP_ID_FROM_CONSOLE",
hostLocationDomId: "HOST_ELEMENT_DOM_ID",
baseUrl: "BASE_URL_FROM_CONSOLE",
displayConfig: {
layoutMode: LayoutMode.MODAL,
title: "Legal Agreements",
subtitle: "Please review and accept the following",
theme: {
primaryColor: "#3b82f6",
backgroundColor: "#ffffff",
},
behavior: {
allowCloseOnBackdrop: false,
allowCloseOnEscape: false,
},
labels: {
acceptButton: "I accept — continue",
cancelButton: "Not now",
},
},
});
await clickwrap.init();
Display Modes
embedded-defaultrenders agreement checkboxes and links insidehostLocationDomId. Use it when the legal copy should feel inline with your signup, onboarding, or checkout form.embedded-agreement-viewerrenders a richer, tabbed reader inside the host element. Use it when users should skim full agreement text before accepting.modaldoes not occupy page layout oninit(). CallopenConsentDialog()when the user hits Submit or an equivalent CTA, so consent blocks progression only when needed.
See Examples for detailed implementations using LayoutMode.EMBEDDED_DEFAULT, LayoutMode.EMBEDDED_AGREEMENT_VIEWER, and LayoutMode.MODAL.
Submit Payload
user_identifier is the only required field for
submit(payload). Use a value that uniquely and persistently
identifies the user in your system — email address or UUID.
The submit payload can also include business context alongside the required
identifier. Use additional_custom_information for arbitrary key-value pairs
that downstream teams should see with the generated Clickwrap contract.
Optional fields
| Field | Description |
|---|---|
first_name | Stored with the contract |
last_name | Stored with the contract |
user_email | Stored with the contract |
additional_custom_information | Arbitrary key-value pairs stored with the contract |
key_pointer_information | Typed metadata fields (see below) |
const contract = await clickwrap.submit({
user_identifier: "johndoe@example.com", // required
first_name: "John",
last_name: "Doe",
user_email: "johndoe@example.com",
// Arbitrary business context stored with the record
additional_custom_information: {
plan: "enterprise",
account_id: "acc_9876",
},
});
SDK Methods
| Method | Returns | Description |
|---|---|---|
init() | Promise<void> | Mount the Clickwrap UI into hostLocationDomId |
isAccepted() | boolean | true when all mandatory agreements are accepted |
submit(payload) | Promise<object> | Record acceptance; resolves with the created clickwrap contract |
openConsentDialog() | void | Open modal (when layoutMode: 'modal') |
closeConsentDialog() | void | Close modal |
isReacceptanceRequired(id) | Promise<object> | Resolve with consent status including whether re-acceptance is required |
on(event, callback) | void | Subscribe to an event |
Event Handling
Subscribe with clickwrap.on(eventName, callback) or use the
SdClickthroughEvents enum (TypeScript) for compile-time safety.
| Event string | When it fires |
|---|---|
acceptanceToggled | User checks or unchecks an agreement; callback receives the aggregate accepted state |
acceptanceComplete | All mandatory agreements are accepted — safe to enable Submit |
cancelClicked | User dismisses the flow |
sdClickthroughLoaded | CDN bootstrap finished; safe to construct the SDK instance |
sdClickthroughLoadFailed | CDN script could not load |
- TypeScript
- JavaScript
import {
SdClickthrough,
SdClickthroughEvents,
} from "@spotdraft/clickwrap-client";
// Enable submit only when all agreements are accepted
clickwrap.on(
SdClickthroughEvents.ACCEPTANCE_TOGGLED,
(isAccepted: boolean) => {
submitButton.disabled = !isAccepted;
}
);
// Track partial acceptance state
clickwrap.on(
SdClickthroughEvents.ACCEPTANCE_TOGGLED,
(isAccepted: boolean) => {
console.log("All accepted:", isAccepted);
}
);
// Enable submit only when all agreements are accepted
clickwrap.on("acceptanceToggled", (isAccepted) => {
submitButton.disabled = !isAccepted;
});
// Track partial acceptance state
clickwrap.on("acceptanceToggled", (isAccepted) => {
console.log("All accepted:", isAccepted);
});
Theming
Pass a theme object inside displayConfig to align the
Clickwrap UI with your design system. All theme fields are optional — unset
values fall back to SpotDraft defaults.
See Examples for complete UI implementations that pair displayConfig with each supported layout.
For deeper styling, the SDK emits stable class names you can target with your own CSS:
sd-clickthrough-checkbox— the agreement checkbox controlsd-clickthrough-text— text nodes or labels adjacent to agreements
Prefer theme tokens first; use CSS selectors only when you need pixel-perfect alignment.
const clickwrap = new SdClickthrough({
clickwrapId: "YOUR_CLICKWRAP_ID",
baseUrl: "YOUR_BASE_URL",
hostLocationDomId: "host-element",
displayConfig: {
theme: {
primaryColor: "#3b82f6",
secondaryColor: "#6b7280",
backgroundColor: "#ffffff",
textColor: "#4a5568",
borderColor: "#e1e5e9",
activeTabColor: undefined,
activeTabTextColor: undefined,
},
},
});
await clickwrap.init();
Re-acceptance
When you publish a new version of an agreement, users who accepted an older version
may need to acknowledge the update. Call
isReacceptanceRequired(user_identifier) on login or session start.
The returned object includes:
status—NOT_ACCEPTED,ACCEPTED_OLDER_VERSION, orACCEPTED_LATEST_VERSIONtrigger_acceptance—trueif your app should show the consent flow again
const result = await clickwrap.isReacceptanceRequired(
"johndoe@example.com"
);
if (result.trigger_acceptance) {
// Re-mount the UI and show the consent flow
await clickwrap.init();
await clickwrap.openConsentDialog();
}
Metadata: key_pointer_information
Map SpotDraft metadata fields by passing a key_pointer_information object on submit. Keys must use the configured slug_ prefix:
const contract = await clickwrap.submit({
user_identifier: "user@example.com",
key_pointer_information: {
slug_user_email: currentUser.email,
slug_displayname: currentUser.displayname,
slug_username: currentUser.username,
},
});
Values are validated against the types you chose when creating each metadata field. If the shape does not match, SpotDraft may omit the field from the UI but contract creation still succeeds.
Metadata types
| Type | Syntax | Example |
|---|---|---|
| STRING | "field": "value" | "slug_name": "Harvey Specter" |
| PARAGRAPH | "field": string | "slug_paragraph": "It's Paragraph KP" |
| DATE | "field": "YYYY-MM-DD" | "slug_dob": "2023-01-01" |
| NUMBER | "field": integer | "slug_age": 24 |
| CHECK BOX | "field": bool | "slug_is_admin": true |
| CURRENCY | "field": { "type": code, "value": int } | "slug_salary": { "type": "USD", "value": 1000 } |
| PHONE NUMBER | "field": { "number", "country_code", "code" } | "slug_mobile": { "number": "9999912345", "country_code": "IN", "code": "+91" } |
| DURATION | "field": { "days", "type", "value" } | "slug_term": { "days": 730, "type": "YEARS", "value": 2 } |
| DROPDOWN | "field": "value" | "slug_country": "India" |
| MULTI DROPDOWN | "field": ["a", "b"] | "slug_tags": ["L1", "L2"] |
Types not available for Clickwrap packet metadata: Address, Multi-file, Image, Repeating, and Related contract.
Examples
These demos are interactive UI mockups that mirror the integration patterns below. Use the Code tab on each example for the production SDK wiring.
Example 1 — Modal
Signup flow
Create your workspace
This demo shows a modal-style consent flow that opens only when needed.
import { LayoutMode, SdClickthrough } from "@spotdraft/clickwrap-client";
const form = document.getElementById("signup-form") as HTMLFormElement;
const emailInput = document.getElementById("email") as HTMLInputElement;
// Keep a hidden host element in your page markup:
// <div id="clickwrap-host" style="display: none"></div>
const clickwrapId = "YOUR_CLICKWRAP_ID";
const baseUrl = "YOUR_BASE_URL";
const clickwrap = new SdClickthrough({
clickwrapId,
baseUrl,
hostLocationDomId: "clickwrap-host",
displayConfig: {
layoutMode: LayoutMode.MODAL,
title: "Terms",
subtitle: "Please review and accept before continuing",
behavior: {
allowCloseOnBackdrop: false,
allowCloseOnEscape: false,
},
},
});
await clickwrap.init();
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (!clickwrap.isAccepted()) {
await clickwrap.openConsentDialog();
return;
}
await submitFinalData();
});
clickwrap.on("acceptanceComplete", async () => {
await submitFinalData();
});
async function submitFinalData() {
await clickwrap.submit({
user_identifier: emailInput.value,
});
form.submit();
}
Example 2 — Embedded checkboxes
Embedded checkbox layout
Accept before continuing
import {
LayoutMode,
SdClickthrough,
SdClickthroughEvents,
} from "@spotdraft/clickwrap-client";
const submitButton = document.getElementById("submit-btn") as HTMLButtonElement;
// Keep these elements in your page markup:
// <div id="clickwrap-checkbox-host"></div>
// <button id="submit-btn" disabled>Sign Up</button>
const clickwrapId = "YOUR_CLICKWRAP_ID";
const baseUrl = "YOUR_BASE_URL";
const clickwrap = new SdClickthrough({
clickwrapId,
baseUrl,
hostLocationDomId: "clickwrap-checkbox-host",
displayConfig: {
layoutMode: LayoutMode.EMBEDDED_DEFAULT,
},
});
await clickwrap.init();
clickwrap.on(SdClickthroughEvents.ACCEPTANCE_TOGGLED, (isAccepted: boolean) => {
submitButton.disabled = !isAccepted;
});
submitButton.addEventListener("click", async () => {
await clickwrap.submit({
user_identifier: "user@example.com",
});
alert("Submitted!");
});
Example 3 — Embedded agreement viewer
Master Services Agreement
This agreement explains the core commercial terms, permitted use, and service commitments for your workspace.
import { LayoutMode, SdClickthrough } from "@spotdraft/clickwrap-client";
const actionButton = document.getElementById("action-btn") as HTMLButtonElement;
// Keep these elements in your page markup:
// <div id="clickwrap-viewer-host" style="height: 400px"></div>
// <button id="action-btn" disabled>Continue</button>
const clickwrapId = "YOUR_CLICKWRAP_ID";
const baseUrl = "YOUR_BASE_URL";
const clickwrap = new SdClickthrough({
clickwrapId,
baseUrl,
hostLocationDomId: "clickwrap-viewer-host",
displayConfig: {
layoutMode: LayoutMode.EMBEDDED_AGREEMENT_VIEWER,
title: "Terms & Conditions",
subtitle: "Review the agreement before submitting",
},
});
await clickwrap.init();
clickwrap.on("acceptanceComplete", () => {
actionButton.disabled = !clickwrap.isAccepted();
});
Regional baseUrl
baseUrl is region-specific. Use the value that matches your SpotDraft workspace region. The View snippet value in the SpotDraft console already points to the correct regional baseUrl.
| Region | baseUrl |
|---|---|
| India | https://api.in.spotdraft.com/api/ |
| United States | https://api.us.spotdraft.com/api/ |
| Middle East | https://api.me.spotdraft.com/api/ |
| European Union | https://api.eu.spotdraft.com/api/ |
Migrating from CDN to NPM
- Install:
npm install @spotdraft/clickwrap-client - Remove the
<script src="https://sdk.spotdraft.com/clickwrap/v1/sdk.js">tag. - Remove
sdClickthroughLoadedwiring — not needed with NPM. - Import
SdClickthroughand callawait sdk.init()after constructing.
- Before (CDN)
- After (NPM)
window.addEventListener("sdClickthroughLoaded", () => {
const sdk = new SdClickthrough({ ... });
sdk.init();
});
import { SdClickthrough } from "@spotdraft/clickwrap-client";
const sdk = new SdClickthrough({ ... });
await sdk.init();
Method signatures (submit, isAccepted, on, openConsentDialog, etc.) are unchanged.
Common Setup Issue
If the SDK returns "Request received from invalid domain", the request origin is not allowlisted in Clickwrap settings. Add the full domain including protocol such as https://app.example.com. localhost is typically allowlisted for local testing.
If you see a 404, CORS failure, or the SDK calls the wrong host, check that baseUrl matches your workspace region. See Regional baseUrl. In past setups, this usually meant the snippet from the wrong regional cluster was copied into the integration.
Related
- Mobile Integration (iOS / Android)
- @spotdraft/clickwrap-client on npm
- Setting up Clickwrap agreements
- Implementing Clickwrap on your website
Help Resources
- Implementing Clickwrap collection
- How to Implement Clickwrap in Your Application
- How to Implement SpotDraft Clickwrap in a Mobile Application