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.
Sample Apps
Prefer to start from working code? Each sample is a minimal end-to-end integration — a small Node backend that mints embed tokens plus a frontend that mounts all three components:
- signstack-vanilla-embed — plain HTML + JavaScript, no framework or bundler
- signstack-react-embed — React + Vite
- signstack-angular-embed — Angular
The Pattern
- 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/embedto mint a short-lived embed token scoped to that user + intent. - Your server returns the token to the browser.
- The browser mounts the SignStack web component and passes the token in.
- The component uses the token for its own API calls; you listen for events (
signed,declined,sessionExpired).
Step 1: Load the Web Components Bundle
Drop the SignStack embed bundle (script + stylesheet) into your app's HTML. Once loaded, the three custom elements register themselves and you can use them anywhere in your DOM.
Track the latest v1 (auto-updates within major version 1, so you get fixes without changing your HTML):
<link rel="stylesheet" href="https://cdn.signstack.ai/embed/v1/signstack.css" />
<script type="module" src="https://cdn.signstack.ai/embed/v1/signstack.js"></script>
Each major version (v1, v2, …) is a stable track — new majors ship breaking changes behind a new path, so your HTML keeps working until you choose to move.
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": "participant",
"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('sessionExpired', onExpired);
return () => {
el.removeEventListener('signed', onSigned);
el.removeEventListener('declined', onDeclined);
el.removeEventListener('sessionExpired', 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 participant component fires a sessionExpired 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. (The builder surfaces an expired/invalid token through its error event; the workflow component emits no lifecycle events, so for long-lived monitor views track the token's expiresAt and remint before it lapses.)
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) | Required body fields | Key events (camelCase) |
|---|---|---|---|
<signstack-participant> |
participant |
workflowId, stepKey |
signed, declined, signingError, sessionExpired, closed |
<signstack-workflow> |
workflow |
workflowId |
(none — persistence is server-side; track expiresAt to remint) |
<signstack-builder> |
builder |
resourceKey, resourceKind (+ optional resourceVersion) |
saved, published, ran, deleted, error |
intent itself can also be omitted in the mint request — the server infers it from the body shape (workflowId + stepKey → participant; workflowId alone → workflow; resourceKey + resourceKind → builder).
For <signstack-workflow> specifically, the token's scopes are adapted to whatever the calling API key holds, and the component fetches the workflow's live status when it mounts:
- Pending workflow + key has
workflow:update→ token grants update; component renders the editor - Pending workflow + read-only key → token is read-only; component renders the monitor
- In-flight workflow + key has
workflow:update→ token grants update; component renders the monitor with operator actions (reminders, cancel) inline - In-flight workflow + read-only key → token is read-only; component renders the monitor (view-only)
- Key without
workflow:read→ 403 at mint time
Status is fetched at mount, not stamped into the token, so transitions between mint and mount don't strand the user in a stale view. No mode attribute on the element, no client-side branching.
For <signstack-builder>, the token's scopes are likewise adapted to whatever the calling API key holds, so the editor's toolbar actions light up only when the key can back them (Edit/Delete, scenario authoring, Publish-to-Library, and Run). The builder runs inside your page with no router of its own, so navigation-style actions don't redirect — they emit events for your app to act on instead:
- Run creates the workflow and emits
ran({ workflowId }). Listen for it and mount a<signstack-workflow>with a freshly-minted workflow token to show the new run. - Delete removes the resource and emits
deleted({ key, version }). The edited resource no longer exists, so unmount the builder (and refresh your own list) in response.
(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.
Related
- Sample apps — Runnable vanilla / React / Angular integrations
- Embedding Components — The conceptual overview and component catalog
- Securely Embedding Components (Session Tokens) — Token mechanics, intent catalog, security model
- Webhooks — Authoritative signal that signing completed
