Developers

Embed the ClinReady Sign client portal in ClinReady, CareVeo, or any host application.

1 · Embed the login widget

Mount the portal in an iframe. On success it posts the session to your app.

<iframe
  src="https://<portal-host>/embed/login?client=clinready&origin=https://app.clinready.com"
  style="width:100%;max-width:440px;height:620px;border:0"
  allow="publickey-credentials-get"
></iframe>

window.addEventListener("message", (e) => {
  if (e.data?.source !== "clinready-portal") return;
  if (e.data.type === "auth:success") {
    // e.data.access_token, e.data.user, e.data.tenants, e.data.default_tenant
  }
});

2 · Redirect flow (no iframe)

Send users to /auth?redirect=/portal. Single-tenant users land directly in their workspace; multi-tenant users get a picker first.

3 · White-label embed theming

Pass logo, accent, brand, radius, and theme (light/dark) query params to any embed URL, or store defaults per organization. Logo URLs must be https; colors are validated CSS color values.

<iframe
  src="https://sign.clinready.com/embed/login?client=clinready&theme=light"
  style="width:100%;max-width:480px;height:640px;border:0"
></iframe>

4 · Embedded signing sessions

Mint a short-lived (~1 hour), single-use token server-to-server for one envelope recipient, then drop the returned URL in an iframe. Only allowed_origin ever receives postMessage events for that session.

// Server-to-server
const res = await fetch("https://<portal-host>/api/public/v1/signing-sessions", {
  method: "POST",
  headers: { "content-type": "application/json", "x-api-key": "<tenant-api-key>" },
  body: JSON.stringify({
    envelope_id: "<envelope-id>",
    recipient_id: "<recipient-id>",
    allowed_origin: "https://app.example.com",
    redirect_url: "https://app.example.com/done",
    ttl_seconds: 3600,
  }),
});
const { url, token, expires_at } = await res.json();

// Client: mount url in an iframe
// <iframe src={url} style="width:100%;max-width:480px;height:720px;border:0"></iframe>

window.addEventListener("message", (e) => {
  if (e.data?.source !== "clinready-portal") return;
  // e.data.type: sign:loaded | sign:viewed | sign:signed | sign:declined | sign:completed | sign:error
});

5 · Outbound webhooks

Register an HTTPS endpoint to receive signed envelope lifecycle events. Every request includes an X-ClinReady-Signature: t=<timestamp>,v1=<hex hmac> header computed as HMAC-SHA256(secret, `${timestamp}.${body}`). Verify it, reject stale timestamps, and compare digests in constant time:

// Node / Web Crypto verification
async function verify(secret, body, header) {
  const [tPart, vPart] = header.split(",");
  const timestamp = tPart.split("=")[1];
  const expected = vPart.split("=")[1];
  const key = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(secret),
    { name: "HMAC", hash: "SHA-256" }, false, ["sign"],
  );
  const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${timestamp}.${body}`));
  const hex = [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, "0")).join("");
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300; // 5 min tolerance
  return fresh && hex === expected;
}

Sign in to an organization workspace to register webhook endpoints.

6 · REST endpoints

GET /api/public/v1/me
Bearer user access token. Returns the profile, every tenant membership with role, and the default tenant when there is exactly one.
GET /api/public/v1/tenant
Header x-api-key with a tenant API key. Returns the tenant and its members — server-to-server only, never from a browser.
POST /api/public/v1/signing-sessions
Header x-api-key. Body: envelope_id, recipient_id, allowed_origin, optional redirect_url, ttl_seconds (default 3600) and theme. Returns an /embed/sign URL, the raw token, and its expiry.
GET /api/public/v1/embed-config?client=clinready
Public. Returns the embed URL, allowed origins, and post-login path for a registered host application.

All endpoints send permissive CORS headers and answer OPTIONS preflight.