Embed SignStack into Your App

This guide is the practical companion to Embedding Components. You'll wire up a working signing flow inside your own app — server-side embed-token issuance, browser-side mounting of the web component, and event handling.

The same pattern applies to all three components (<signstack-participant>, <signstack-builder>, <signstack-workflow>); the differences are which intent you mint a token for and which props each component takes.

The Pattern

  1. Your server holds the long-lived API key. When a logged-in user wants to access an embedded surface (e.g. sign a document), your server calls SignStack's POST /v1/orgs/{orgId}/namespaces/{namespaceKey}/auth/embed to mint a short-lived embed token scoped to that user + intent.
  2. Your server returns the token to the browser.
  3. The browser mounts the SignStack web component and passes the token in.
  4. The component uses the token for its own API calls; you listen for events (signed, declined, session-expired).

Step 1: Load the Web Components Bundle

Drop the SignStack web components script into your app's HTML. Once loaded, the three custom elements register themselves and you can use them anywhere in your DOM:

<script type="module" src="https://signstack.ai/web-components.js"></script>

Step 2: Mint an Embed Token (Server-Side)

For an embedded signing flow:

curl -X POST https://api.signstack.ai/v1/orgs/{orgId}/namespaces/{namespaceKey}/auth/embed \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "signing_session",
    "workflowId": "b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f",
    "stepKey": "consultant_signs",
    "expiresIn": 900,
    "allowedOrigins": ["https://yourapp.com"],
    "context": {
      "recipientEmail": "jane@example.com",
      "recipientName": "Jane Doe"
    }
  }'

Response:

{
  "accessToken": "eyJhbGciOi…",
  "orgId": "f1e2d3c4-b5a6-4789-a012-345678901234",
  "tokenType": "Bearer",
  "expiresIn": 900,
  "expiresAt": "2026-04-19T16:30:00.000Z",
  "scopes": ["..."],
  "subject": { "type": "embed", "id": "8a9b0c1d-2e3f-4a5b-6c7d-8e9f0a1b2c3d", "orgId": "f1e2d3c4-b5a6-4789-a012-345678901234" },
  "resources": [{ "type": "workflow", "id": "b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f", "actions": ["sign"] }]
}

accessToken is what the browser receives. Pass allowedOrigins to scope the token to your domain (browser CORS protection); set expiresIn to the shortest reasonable window for your UX.

The full intent catalog and security rationale lives in Securely Embedding Components.

Step 3: Mount the Component (Browser-Side)

<signstack-participant embed-token="eyJhbGciOi…"></signstack-participant>

Everything the component needs (org_id, namespace_key, workflow_id, step_key) is carried as claims inside the token — no separate attributes are required.

In React (custom elements work natively):

function SigningPage({ embedToken }) {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    const onSigned   = (e) => console.log('signed', e.detail);
    const onDeclined = (e) => console.log('declined', e.detail);
    const onExpired  = () => refetchEmbedToken();
    el.addEventListener('signed', onSigned);
    el.addEventListener('declined', onDeclined);
    el.addEventListener('session-expired', onExpired);
    return () => {
      el.removeEventListener('signed', onSigned);
      el.removeEventListener('declined', onDeclined);
      el.removeEventListener('session-expired', onExpired);
    };
  }, []);

  return <signstack-participant ref={ref} embed-token={embedToken} />;
}

The component picks up your app's CSS for typography, colors, and surrounding chrome — no theme-override config to manage.

Step 4: Handle Token Expiry

Embed tokens are deliberately short-lived. When one expires, the component fires a session-expired event. Your handler should call your server to mint a fresh token and update the component's embed-token attribute. The component will reconnect with the new token.

Component Reference

All three components take embed-token as their only required attribute — every other detail (org, namespace, workflow ID, step key, resource info) is carried as JWT claims inside the token. The component decodes them on mount.

Component Intent (token mint) Optional attrs Key events
<signstack-participant> signing_session signed, declined, signing-error, session-expired, closed
<signstack-workflow> workflow_editing or workflow_monitoring (one per mount) mode="editor"|"monitor" saved, published, session-expired
<signstack-builder> resource_editing saved, published, session-expired

<signstack-workflow> picks the editor or monitor view automatically from the workflow's current status (pending → editor, otherwise → monitor). Pass mode only to force a specific view — note that the embed token's intent must match (workflow_editing for editor, workflow_monitoring for monitor).

(The participant component runs the actual signature capture in a sandboxed context for security; the others render inline within your DOM.)

Common Pitfalls

  • Forgetting allowedOrigins — Tokens are usable from any browser without it. Always pass your app's exact origin(s).
  • Reusing tokens across users — Each token is scoped to one user/intent. Mint per-session; don't cache server-side.
  • Long expiresIn — Default to 15 minutes. Long-lived embed tokens defeat the security model.
  • Treating events as authoritative — Always confirm signing completion via webhooks or workflow status; the browser event is a UX signal, not a source of truth.