# Embedded Contract Signing

The Embedded Contract Signing SDK lets you render a loan agreement for signature directly inside your own web application, so applicants never leave your experience. You load a small JavaScript SDK, hand it a signing token from the Quantum API, and subscribe to a handful of Quantum-shaped events to track progress.

The SDK is a thin, vendor-neutral wrapper. Your integration is written against the Quantum API surface described here — not against whichever document provider Quantum uses under the hood — so the contract you build to stays stable even if the underlying provider changes.

## Table of Contents

- [How It Works](#how-it-works)
- [Get a Signing Token](#get-a-signing-token)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Events](#events)
- [Error Handling](#error-handling)
- [Lifecycle Methods](#lifecycle-methods)
- [Embedding & Security](#embedding-and-security)
- [Try It Live](#try-it-live)
- [Next Steps](#next-steps)

---

## How It Works

The embedded signing flow has three steps:

```
1. YOUR BACKEND REQUESTS A SIGNING TOKEN
   └─→ GET /api/v3/applications/{app_id}/contracts
   └─→ Response includes a signing_token for each signer

2. YOUR FRONTEND LOADS THE SDK
   └─→ Load the SDK from the Quantum CDN
   └─→ new QuantumContracts(container, { signingToken })
   └─→ qc.open()

3. THE APPLICANT SIGNS IN YOUR PAGE
   └─→ The SDK renders the signing experience inside your container
   └─→ You receive contract.loading / loaded / ready / completed events
   └─→ contract.completed fires once the signer finishes
```

The signing token is short-lived and scoped to a single signer on a single contract. Fetch it from your backend (where your API credentials live) and pass it to the browser only when you are ready to render the signing experience.

---

## Get a Signing Token

{% endpoint method="GET" path="/api/v3/applications/{app_id}/contracts" /%}

Call this endpoint from your backend once an application has reached the document-signing stage. The response lists the contract's signers, each with a `signing_token` you hand to the SDK.

{% callout type="info" %}
The `signing_token` is a credential. Request it from your backend using your API token, then deliver it to the browser over a secure channel only when you are about to render the signing experience. Treat it like a session token — never log it, and never embed it in a URL.
{% /callout %}

See the [API Reference](/api-reference.html) for the full request and response schema.

---

## Installation

### CDN (recommended): pinned version with Subresource Integrity

Pin to an exact version and verify the bundle with [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (SRI). The integrity hash is published as a sidecar alongside each release, so you can confirm the bytes you load are the bytes Quantum published.

```html
<div id="contract-container"></div>
<script
  src="https://cdn.quantumlends.com/packages/contracts-sdk/v1.0.0/index.umd.js"
  integrity="sha384-<see-sidecar>"
  crossorigin="anonymous"
></script>
```

Look up the hash for the version you are pinning:

```bash
curl https://cdn.quantumlends.com/packages/contracts-sdk/v1.0.0/index.umd.js.integrity
# → sha384-<base64-hash>
```

The CDN bundle (UMD) exposes a single global namespace, `QuantumSDK`:

```js
const { QuantumContracts, KNOWN_ERROR_CODES, SDK_VERSION } = QuantumSDK;
```

### CDN (convenience): floating major version

If you would rather pick up patch and minor releases automatically, load the floating major-version URL. The trade-off is that you cannot use SRI with a floating tag — the hash changes whenever we publish an update, so the `integrity` attribute would reject the new bundle.

```html
<script src="https://cdn.quantumlends.com/packages/contracts-sdk/v1/index.umd.js"></script>
```

{% callout type="warning" %}
Choose deliberately. Pin + SRI gives you supply-chain protection; the floating tag gives you hands-off updates. Most integrations should pin to an exact version with SRI and bump it on your own schedule.
{% /callout %}

---

## Quick Start

A complete integration is small. The example below loads the SDK from the CDN, opens a contract, and reacts to the lifecycle events.

```html
<div id="contract-container"></div>

<script
  src="https://cdn.quantumlends.com/packages/contracts-sdk/v1.0.0/index.umd.js"
  integrity="sha384-<see-sidecar>"
  crossorigin="anonymous"
></script>

<script>
  const qc = new QuantumSDK.QuantumContracts("contract-container", {
    signingToken: tokenFromYourBackend, // GET /api/v3/applications/{app_id}/contracts
    contractId: "your-internal-id",     // optional; echoed back in every event
  });

  qc.on("contract.loaded", (p) => console.log("loaded", p.contractId));
  qc.on("contract.completed", (p) => {
    // The signer finished — advance your UI / notify your backend.
    window.location.assign("/thank-you");
  });
  qc.on("contract.error", (p) => console.error(p.code, p.message));

  qc.open();
</script>
```

{% callout type="info" %}
Server-side completion notification is separate from this SDK. Use the `contract.completed` event for in-page UX, but rely on Quantum webhooks for authoritative, server-confirmed completion. See the [Webhooks Integration Guide](/webhooks-guide.html).
{% /callout %}

---

## Configuration

Construct the SDK with a container and a configuration object:

```ts
new QuantumContracts(container, config);
```

**`container`** — either the `id` of an element (a string) or an `HTMLElement` reference. The SDK renders the signing experience inside this element.

**`config`** options:

{% parameter name="signingToken" type="string" required=true %}
The signing token for a single signer, obtained from `GET /api/v3/applications/{app_id}/contracts`.
{% /parameter %}

{% parameter name="contractId" type="string" required=false %}
Your own identifier for this contract. The SDK never reads or derives it from the provider — it simply echoes the value you pass back in every event payload, so you can correlate events with your own records.
{% /parameter %}

{% parameter name="width" type="number | string" required=false %}
Width of the signing container. A `number` is applied as pixels (`600` → `600px`); a `string` is applied verbatim (`"100%"`, `"50vw"`). Defaults to leaving your container's styling untouched.
{% /parameter %}

{% parameter name="height" type="number | string" required=false %}
Height of the signing container. Same handling as `width`. A typical full-page signing experience is around `965` pixels.
{% /parameter %}

{% parameter name="debug" type="boolean" required=false %}
Enables verbose console logging for troubleshooting. The signing token is redacted in debug output. Defaults to `false`.
{% /parameter %}

---

## Events

Subscribe with `on(eventName, handler)`. Every payload includes the `contractId` you supplied in the constructor (or `undefined` if you did not supply one).

| Event | When it fires | Payload |
|-------|---------------|---------|
| `contract.loading` | The signing experience is initializing | `{ contractId? }` |
| `contract.loaded` | The contract document is loaded and visible | `{ contractId? }` |
| `contract.ready` | The document has rendered all of its pages | `{ contractId? }` |
| `contract.completed` | The signer has completed all required fields and submitted | `{ contractId? }` |
| `contract.error` | An error occurred — inspect `code` | `{ contractId?, code, message? }` |

```js
qc.on("contract.loading",   (p) => updateStatus("Loading…"));
qc.on("contract.loaded",    (p) => updateStatus("Awaiting signature"));
qc.on("contract.ready",     (p) => updateStatus("Document ready"));
qc.on("contract.completed", (p) => updateStatus("Signed"));
qc.on("contract.error",     (p) => showError(p.code, p.message));
```

{% callout type="info" %}
The event set is intentionally small and may grow. Additional events (for example, a future `contract.declined`) can be added in a minor release without breaking your existing handlers, since you only receive the events you subscribe to.
{% /callout %}

---

## Error Handling

All failures surface through a single `contract.error` event carrying a `code` and an optional sanitized `message`. This gives you one consistent place to handle problems.

| `code` | Meaning |
|--------|---------|
| `not_found` | The signing token is invalid or its document is no longer available |
| `verification_required` | The signer must complete an identity verification step first |
| `verification_limit_exceeded` | The signer exhausted their verification attempts; signing cannot proceed |
| `loading_error` | The signing experience failed to initialize (network, container, or provider issue) |
| `unknown` | The provider signaled a failure without a specific code |

```js
qc.on("contract.error", (p) => {
  switch (p.code) {
    case "not_found":
      // Token expired or invalid — fetch a fresh signing_token and retry.
      break;
    case "verification_required":
    case "verification_limit_exceeded":
      // Surface a verification message to the signer.
      break;
    default:
      // Future codes you don't recognize yet — log and show a generic message.
      console.error("Signing error:", p.code, p.message);
  }
});
```

{% callout type="warning" %}
The `code` list is **not exhaustive** — new codes may be added in any release. Always include a `default` branch when switching on `code` so your handler stays forward-compatible. The runtime export `KNOWN_ERROR_CODES` lists the codes known to the version you loaded.
{% /callout %}

The optional `message` is a human-readable description with provider-specific identifiers and hostnames stripped out. It is safe to log, but it is intended for diagnostics — drive your UX off `code`, not off the message text.

---

## Lifecycle Methods

| Method | Description |
|--------|-------------|
| `open()` | Renders the signing experience and resolves once the session is open. Returns a `Promise<void>`. |
| `close()` | Tears down the signing experience and restores a clean container. The instance stays usable — you can `open()` again with the same or a new token. |
| `destroy()` | Permanently tears down the instance and unsubscribes all handlers. The instance cannot be reused afterward. |
| `on(event, handler)` | Subscribe to an event. Returns the instance, so calls can be chained. |
| `once(event, handler)` | Subscribe to the next occurrence of an event only. |
| `off(event, handler)` | Remove a previously registered handler. |

```js
const qc = new QuantumContracts("contract-container", { signingToken });

qc.on("contract.completed", () => qc.destroy()); // clean up when done
await qc.open();

// Later, to swap to a different contract in the same container:
qc.close();
qc.open(); // after assigning a new token, if needed
```

{% callout type="info" %}
Vendor-side failures emit a `contract.error` event rather than rejecting the `open()` promise, so you have a single error channel. It is safe to call `open()` again from inside a `contract.error` handler to retry.
{% /callout %}

---

## Embedding & Security {% #embedding-and-security %}

The SDK renders the signing experience in an `<iframe>` that it injects into the container you provide. Because that iframe is served from the signing provider's origin (not yours), there are a few things to get right when you embed it in a security-conscious application.

### Content Security Policy (CSP)

If your application sets a `Content-Security-Policy`, allow two things: loading the SDK script from the Quantum CDN, and framing the embedded signing experience.

```
Content-Security-Policy:
  script-src 'self' https://cdn.quantumlends.com;
  frame-src  https:;
```

- **`script-src`** must include `https://cdn.quantumlends.com` so the browser can load the SDK bundle (and verify it against your `integrity` hash).
- **`frame-src`** (or `child-src` on older browsers) must allow the signing experience to render in an iframe. The SDK embeds the signing iframe over HTTPS, so a `frame-src https:` directive keeps your policy working regardless of which signing provider backs the SDK.

{% callout type="info" %}
Want a `frame-src` tighter than `https:`? You can allowlist the specific signing-provider origin instead — contact your Quantum representative for the current value. Note that a strict allowlist would need updating if the provider changes. We're working toward serving the signing experience from a Quantum-owned origin, after which a single `https://*.quantumlends.com` entry will be all you need.
{% /callout %}

### X-Frame-Options and frame-ancestors

`X-Frame-Options` and the CSP `frame-ancestors` directive control who is allowed to frame *your* page. They do not affect the signing iframe the SDK creates — that is governed by the provider's own headers, and the provider permits embedding by design.

You only need to think about these headers if your own page (the one hosting the SDK) is itself rendered inside another frame:

- If you serve your page with `X-Frame-Options: DENY` or `SAMEORIGIN`, it cannot be embedded cross-origin. That is fine for a top-level page that hosts the SDK directly.
- If you intend to embed your SDK-hosting page inside another site, set `Content-Security-Policy: frame-ancestors` to list the permitted parents instead of using the legacy `X-Frame-Options`.

### General notes

- Serve the page over HTTPS. The signing experience and its token exchange require a secure context.
- The signing iframe runs in the provider's origin and handles the signing token directly. Keep the token's exposure to the browser as brief as possible — fetch it from your backend at render time rather than storing it client-side.
- Give each SDK instance its own container element. If you embed two contracts on one page, each needs a distinct container `id`.

---

## Try It Live {% #try-it-live %}

Want to see it before you build? The [Embedded Signing Demo](/embedded-signing-demo.html) loads the live SDK in your browser. Paste a signing token from your environment, open a contract, and watch the events stream in real time.

---

## Next Steps

- Explore the live [Embedded Signing Demo](/embedded-signing-demo.html)
- Review the [API Reference](/api-reference.html) for the `signing_token` endpoint schema
- Set up [Webhook Subscriptions](/webhooks-guide.html) for authoritative, server-confirmed completion
- Contact your Quantum representative for signing-provider release notes
