Skip to main content

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:

ImplementationBest forGuide
Web (npm, recommended)React, Next.js, Angular, Vue, and other bundled appsThis page
Web (CDN)Plain HTML pages or no-build-step setupsThis page — CDN section
Mobile (iOS / Android)Native iOS or Android appsMobile 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 install @spotdraft/clickwrap-client

2. Copy your values from SpotDraft

In the SpotDraft Clickwrap console, open View snippet for your Clickwrap packet and copy:

  • clickwrapId — identifies your Clickwrap packet
  • baseUrl — 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.

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;
});

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:

  1. The agreement UI renders inside clickwrap-host.
  2. The Submit button stays disabled until all required agreements are accepted.
  3. 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 Referer sent by the browser is allowed. If the domain is missing you may see "Request received from invalid domain". localhost is allowlisted by default for local testing.
  • HTTPS in production — Serve integration pages over HTTPS in staging and production. Local http://localhost is 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 install @spotdraft/clickwrap-client

ES modules (React, Angular, Vue, etc.):

import {
SdClickthrough,
SdClickthroughEvents,
} from "@spotdraft/clickwrap-client";

CommonJS:

const { SdClickthrough } = require("@spotdraft/clickwrap-client");

Initialization

  1. Instantiate new SdClickthrough({ ... }) with at least the three required fields below.
  2. Call await init() (NPM) or init() (CDN) so the SDK can mount into the DOM for inline layouts. modal layout often mounts nothing until you call openConsentDialog().
FieldPurpose
clickwrapIdPublic identifier of the Clickwrap shown on the Clickwrap settings page
hostLocationDomIdThe id of a DOM node in your page where the Clickwrap UI should render
baseUrlAPI 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

PropertyTypeDefaultDescription
layoutModestring | LayoutMode'embedded-default''embedded-default', 'embedded-agreement-viewer', or 'modal'
titlestringHeader title (viewer / modal)
subtitlestringHeader subtitle (viewer / modal)
tabOrientationstring'horizontal''horizontal', 'vertical-left', 'vertical-right'
behavior.allowCloseOnBackdropbooleanfalseLet modal close on backdrop click
behavior.allowCloseOnEscapebooleanfalseLet modal close on Escape key
labels.acceptButtonstring"Accept and continue"Override the primary CTA label
labels.cancelButtonstring"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

  1. embedded-default renders agreement checkboxes and links inside hostLocationDomId. Use it when the legal copy should feel inline with your signup, onboarding, or checkout form.
  2. embedded-agreement-viewer renders a richer, tabbed reader inside the host element. Use it when users should skim full agreement text before accepting.
  3. modal does not occupy page layout on init(). Call openConsentDialog() 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

FieldDescription
first_nameStored with the contract
last_nameStored with the contract
user_emailStored with the contract
additional_custom_informationArbitrary key-value pairs stored with the contract
key_pointer_informationTyped 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

MethodReturnsDescription
init()Promise<void>Mount the Clickwrap UI into hostLocationDomId
isAccepted()booleantrue when all mandatory agreements are accepted
submit(payload)Promise<object>Record acceptance; resolves with the created clickwrap contract
openConsentDialog()voidOpen modal (when layoutMode: 'modal')
closeConsentDialog()voidClose modal
isReacceptanceRequired(id)Promise<object>Resolve with consent status including whether re-acceptance is required
on(event, callback)voidSubscribe to an event

Event Handling

Subscribe with clickwrap.on(eventName, callback) or use the SdClickthroughEvents enum (TypeScript) for compile-time safety.

Event stringWhen it fires
acceptanceToggledUser checks or unchecks an agreement; callback receives the aggregate accepted state
acceptanceCompleteAll mandatory agreements are accepted — safe to enable Submit
cancelClickedUser dismisses the flow
sdClickthroughLoadedCDN bootstrap finished; safe to construct the SDK instance
sdClickthroughLoadFailedCDN script could not load
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);
}
);

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 control
  • sd-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:

  • statusNOT_ACCEPTED, ACCEPTED_OLDER_VERSION, or ACCEPTED_LATEST_VERSION
  • trigger_acceptancetrue if 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

TypeSyntaxExample
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

Use `LayoutMode.MODAL` to keep the page clean and open consent only when the user is ready to submit. `displayConfig.title` and `displayConfig.subtitle` control the modal header, and `behavior.allowCloseOnBackdrop` / `behavior.allowCloseOnEscape` can tighten modal behavior when needed.

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

Use `LayoutMode.EMBEDDED_DEFAULT` for the simplest inline form layout. This is the default behavior, so no `displayConfig.layoutMode` is required unless you want to make that choice explicit.

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

Use `LayoutMode.EMBEDDED_AGREEMENT_VIEWER` when users should read the agreement in a richer in-page experience. `displayConfig.title` and `displayConfig.subtitle` help frame the viewer context.

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.

RegionbaseUrl
Indiahttps://api.in.spotdraft.com/api/
United Stateshttps://api.us.spotdraft.com/api/
Middle Easthttps://api.me.spotdraft.com/api/
European Unionhttps://api.eu.spotdraft.com/api/

Migrating from CDN to NPM

  1. Install: npm install @spotdraft/clickwrap-client
  2. Remove the <script src="https://sdk.spotdraft.com/clickwrap/v1/sdk.js"> tag.
  3. Remove sdClickthroughLoaded wiring — not needed with NPM.
  4. Import SdClickthrough and call await sdk.init() after constructing.
window.addEventListener("sdClickthroughLoaded", () => {
const sdk = new SdClickthrough({ ... });
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.


Help Resources

References