Embed guide

In-app signing with one component

Render the complete Signvoy signing experience directly inside your product — no redirect, no pop-up. Your server mints a scoped URL, you drop in <SignvoyEmbed url={url} />, and listen for events.

Zero redirects
postMessage events
React + vanilla iframe
Auto-expires

How it works

The embed flow is two steps: your backend mints a short-lived URL, your frontend renders it in an iframe.

1

Mint a session

Your server calls POST /v1/embed/session with a documentId and documentRecipientId. Signvoy returns a scoped signing URL that expires in 1 hour (or up to 24 h).

2

Return the URL to your frontend

Pass the URL from your API response to the browser. Never include your Signvoy API key in client-side code.

3

Render <SignvoyEmbed />

Drop <SignvoyEmbed url={url} /> into your React tree. It renders a sandboxed iframe and wires up postMessage listeners automatically.

4

Handle events

React to onCompleted, onDeclined, and onError callbacks to update your UI, redirect the user, or trigger downstream workflows.

Install @signvoy/react

The @signvoy/react package ships the <SignvoyEmbed /> component plus full TypeScript types. Install it in your React / Next.js project:

terminal
# npm
npm install @signvoy/react

# pnpm
pnpm add @signvoy/react

# yarn
yarn add @signvoy/react

Requires React 18+ and a browser environment. The package has no server-side rendering restrictions — it marks the component as "use client" internally.

Create an embed session

Call POST /v1/embed/session from your server (never from the browser — it requires your API key). The document must be in sent state and the recipient must be pending.

curl
curl -X POST https://api.signvoy.com/v1/embed/session \
  -H "Authorization: Bearer $SIGNVOY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "doc-uuid",
    "documentRecipientId": "recipient-uuid",
    "expiresInSeconds": 3600
  }'

Response:

response.json
{
  "url": "https://signvoy.com/sign/abc123?embed=1",
  "expiresAt": "2026-07-03T11:45:00Z",
  "documentId": "doc-uuid",
  "documentRecipientId": "recipient-uuid"
}
Idempotent: If the recipient already has an active signing token, the same URL is returned. A new token is only minted when the previous one is revoked or expired.

TypeScript — Next.js Server Action example:

actions/embed.ts
"use server";

interface EmbedSessionResponse {
  url: string;
  expiresAt: string;
  documentId: string;
  documentRecipientId: string;
}

export async function createEmbedSession(
  documentId: string,
  recipientId: string,
): Promise<EmbedSessionResponse> {
  const res = await fetch("https://api.signvoy.com/v1/embed/session", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SIGNVOY_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ documentId, documentRecipientId: recipientId }),
    cache: "no-store",
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message ?? `Embed session failed: ${res.status}`);
  }

  return res.json() as Promise<EmbedSessionResponse>;
}

Render <SignvoyEmbed />

Pass the URL from your server action to the component. Size the wrapper to fit your layout — the iframe fills 100 % of its container.

SigningModal.tsx
"use client";

import { useCallback } from "react";
import { useRouter } from "next/navigation";
import { SignvoyEmbed } from "@signvoy/react";

interface Props {
  embedUrl: string;
  documentId: string;
}

export function SigningModal({ embedUrl, documentId }: Props) {
  const router = useRouter();

  const handleCompleted = useCallback(() => {
    // Redirect to a confirmation page when signing is done
    router.push(`/documents/${documentId}/signed`);
  }, [router, documentId]);

  const handleDeclined = useCallback(({ reason }: { reason?: string }) => {
    console.warn("Signing declined:", reason);
    // Show a toast, update state, etc.
  }, []);

  return (
    <div style={{ width: "100%", height: "600px" }}>
      <SignvoyEmbed
        url={embedUrl}
        onCompleted={handleCompleted}
        onDeclined={handleDeclined}
        onError={(e) => console.error("Embed error:", e.message)}
      />
    </div>
  );
}

The component renders a sandboxed <iframe> with allow="camera; clipboard-write" and a tightly scoped sandbox attribute — no cross-origin data leaks.

postMessage events

The signing frame communicates with your page via window.postMessage. Each message has a type string and a payload object. <SignvoyEmbed /> handles the listener boilerplate — just pass callbacks.

Event typeFires whenPayload shape
signvoy:completedRecipient finishes signing all fields{ documentId, documentRecipientId, completedAt }
signvoy:declinedRecipient clicks "Decline"{ documentId, documentRecipientId, reason? }
signvoy:errorAn unrecoverable error occurs in the signing frame{ message }

Vanilla iframe (no React SDK)

If you're not using React, embed the URL in a plain <iframe> and listen for messages manually:

embed.html
const iframe = document.createElement("iframe");
iframe.src = embedUrl; // from your server
iframe.style.width = "100%";
iframe.style.height = "600px";
iframe.style.border = "none";
iframe.allow = "camera; clipboard-write";
iframe.sandbox.add(
  "allow-scripts",
  "allow-same-origin",
  "allow-forms",
  "allow-popups",
  "allow-top-navigation",
);
document.getElementById("signing-container")?.appendChild(iframe);

// Listen for Signvoy events
window.addEventListener("message", (event) => {
  // Only accept messages from the iframe origin
  if (event.origin !== new URL(embedUrl).origin) return;

  const { type, payload } = event.data ?? {};
  if (type === "signvoy:completed") {
    console.log("Signed!", payload);
  } else if (type === "signvoy:declined") {
    console.warn("Declined:", payload?.reason);
  }
});

Security checklist

Never mint sessions client-side

The /v1/embed/session endpoint requires your API key — call it from a server action, API route, or backend service only.

Validate the postMessage origin

Always check event.origin === the signing frame's origin before processing messages. <SignvoyEmbed /> does this for you.

Set a tight expiry

Use expiresInSeconds to match your UX flow. Default is 3 600 s (1 h). Maximum is 86 400 s (24 h). Expired URLs show an error page.

One URL per page load

Mint a fresh session for each user session or page load. Reusing a URL after completion results in an error — the signing token is consumed.

Never expose sk_live_… API keys in browser bundles, client components, or public repositories. Rotate any key that may have been leaked from the Workspace Settings → API Keys page immediately.

Stop chasing signatures

Join thousands of teams using Signvoy to close deals, onboard clients, and sign contracts — faster.

No credit card · Free forever plan · 14-day Pro trial