Securely Embedding Components (Session Tokens)

SignStack provides four embeddable web components for integrating signing, workflow editing, workflow monitoring, and primitive editing directly into your application. To securely power these embedded experiences, SignStack uses Embed Tokens (also called Session Tokens).

This guide explains how embed tokens work and how to use them securely. For the component catalog and conceptual overview, see Embedding Components.

What Are Embed Tokens?

Embed tokens are short-lived, scoped JWT tokens designed specifically for frontend embedded components. Unlike API Keys (which are long-lived secrets for server-to-server communication), embed tokens are safe to use in browser environments because they:

  • Expire quickly (typically 15–60 minutes)
  • Are scoped to a specific intent (a single workflow + step for signing, a single workflow for editing or monitoring, a single primitive version for resource editing — never namespace-wide write access)
  • Have limited permissions (only what's needed for that embed intent)
  • Can be restricted by origin (CORS protection)

Embed Intents

SignStack supports four embed intents, one per embeddable web component. The intent you choose determines which scopes the token carries and which component can consume it.

The JSON blocks below show the request body you POST to /v1/orgs/{orgId}/namespaces/{namespaceKey}/auth/embed for each intent. The full request/response cycle (with the curl envelope and the response shape) is in Generating Embed Tokens below.

1. Signing Session (signing_session)

Powers <ss-signing-embed>. Used when a participant needs to sign documents within your application.

Request body:

{
  "intent": "signing_session",
  "workflowId": "b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f",
  "stepKey": "candidate_signs",
  "expiresIn": 900,
  "allowedOrigins": ["https://yourapp.com"],
  "context": {
    "recipientEmail": "signer@example.com",
    "recipientName": "John Doe"
  }
}

stepKey is required — the token is scoped to a single participant step.

2. Workflow Editing (workflow_editing)

Powers <ss-workflow-editor>. Used to view and edit a workflow's documents and input data — either before launch (filling in inputs, attaching documents) or while it's in flight (making allowed in-flight modifications between signing steps).

Request body:

{
  "intent": "workflow_editing",
  "workflowId": "b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f",
  "expiresIn": 3600,
  "allowedOrigins": ["https://yourapp.com"]
}

3. Workflow Monitoring (workflow_monitoring)

Powers <ss-workflow-monitor>. Renders a live status surface for a single in-flight workflow — current step, participant statuses, history — and lets the viewer take limited operator actions: nudge participants with reminder emails and cancel the workflow.

Request body:

{
  "intent": "workflow_monitoring",
  "workflowId": "b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f",
  "expiresIn": 3600,
  "allowedOrigins": ["https://yourapp.com"]
}

4. Resource Editing (resource_editing)

Powers <ss-resource-editor>. Used to let an end user edit a primitive (blueprint, template, etc.) inside your app — typically when you've handed someone a starter from the Library.

Request body:

{
  "intent": "resource_editing",
  "resourceKind": "template",
  "resourceKey": "offer_letter",
  "resourceVersion": "1.2.0",
  "expiresIn": 3600,
  "allowedOrigins": ["https://yourapp.com"]
}

resourceVersion defaults to the most recent version of the resource if omitted.

Generating Embed Tokens

Embed tokens are generated server-side using your API access token, then passed to your frontend.

Step 1: Request an Embed Token (Server-Side)

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": "candidate_signs",
    "expiresIn": 900,
    "allowedOrigins": ["https://yourapp.com"],
    "context": {
      "recipientEmail": "signer@example.com"
    }
  }'

<ACCESS_TOKEN> is the JWT you get from POST /v1/auth/token — see A Full Guide to API Keys and JWTs.

Step 2: Response

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "orgId": "f1e2d3c4-b5a6-4789-a012-345678901234",
  "tokenType": "Bearer",
  "expiresIn": 900,
  "expiresAt": "2026-04-20T16:45:00.000Z",
  "scopes": ["workflow:sign"],
  "subject": {
    "type": "embed",
    "id": "8a9b0c1d-2e3f-4a5b-6c7d-8e9f0a1b2c3d",
    "orgId": "f1e2d3c4-b5a6-4789-a012-345678901234",
    "namespaceKey": "acme-prod",
    "mode": "live"
  },
  "resources": [
    {
      "type": "workflow",
      "id": "b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f",
      "steps": ["candidate_signs"],
      "actions": ["sign", "view"]
    }
  ]
}

Step 3: Pass to Frontend

Return only the embed accessToken to your frontend. Never expose your API Key or your long-lived org access token to the browser.

// Your backend endpoint
app.post('/api/get-signing-token', async (req, res) => {
  const { workflowId, stepKey, signerEmail } = req.body;

  const resp = await fetch(
    `https://api.signstack.ai/v1/orgs/${ORG_ID}/namespaces/${NAMESPACE_KEY}/auth/embed`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${await getOrgAccessToken()}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        intent: 'signing_session',
        workflowId,
        stepKey,
        expiresIn: 900,
        allowedOrigins: ['https://yourapp.com'],
        context: { recipientEmail: signerEmail },
      }),
    }
  );
  const { accessToken } = await resp.json();
  res.json({ token: accessToken });
});

Mounting the Component

Pass the embed token into the matching SignStack web component as the embed-token attribute:

<ss-signing-embed
  workflow-id="b6f3a8d2-1c4e-4b9a-a7f3-2e8c1d5a9b4f"
  embed-token="eyJhbGciOiJIUzI1NiIs..."
  org-id="f1e2d3c4-b5a6-4789-a012-345678901234"
  namespace-key="acme-prod"
></ss-signing-embed>

Listen for component-fired events (signed, declined, session-expired, etc.) to react to user actions. Full mounting walkthrough — including React and the per-component prop catalog — is in Embed SignStack into Your App.

Security Best Practices

1. Always Generate Tokens Server-Side

Never expose your API Key or long-lived access tokens to the frontend. Always have your backend mint embed tokens and pass only those to the client.

  User Browser                Your Backend                  SignStack API
  ────────────                ────────────                  ─────────────
                              holds API Key (long-lived)
                              + Access Token (1h, derived
                              from API Key) — neither ever
                              leaves the backend
       │                          │                              │
       │  (1) user action → ask backend for an embed token       │
       │ ───────────────────────► │                              │
       │                          │                              │
       │                          │  (2) POST /v1/orgs/.../auth/embed
       │                          │      Authorization: Bearer <Access Token>
       │                          │ ───────────────────────────► │
       │                          │                              │
       │                          │            Embed Token       │
       │                          │ ◄─────────────────────────── │
       │                          │                              │
       │  (3) backend returns only the Embed Token               │
       │ ◄─────────────────────── │                              │
       │                                                         │
       │  (4) <ss-* embed-token="..."> talks to SignStack directly
       │ ──────────────────────────────────────────────────────► │

Only the per-session Embed Token ever reaches the browser. The API Key and the long-lived Access Token stay on your backend at all times.

2. Use Short Expiration Times

Set expiresIn to the minimum window your UX actually needs. Suggested starting points:

  • Signing sessions (signing_session): 15–30 minutes
  • Workflow editing (workflow_editing): 30–60 minutes
  • Workflow monitoring (workflow_monitoring): 15–60 minutes (handle session-expired for long-running views)
  • Resource editing (resource_editing): 30–60 minutes

3. Restrict Allowed Origins

Always specify allowedOrigins to prevent your embed tokens from being used on unauthorized domains:

{
  "allowedOrigins": [
    "https://yourapp.com",
    "https://staging.yourapp.com"
  ]
}

4. Authorize Before Minting

The SignStack API trusts that your server has already verified the requesting user is allowed to access the resource the token is being minted for. Run that check before calling /auth/embed:

// For a signing token: verify this user is the assigned signer
const workflow = await db.workflows.findById(workflowId);
if (workflow.orgId !== user.orgId) {
  throw new ForbiddenError('Access denied');
}
if (workflow.assignedSignerEmail !== user.email) {
  throw new ForbiddenError('Not your signing step');
}

Apply the equivalent check for each intent — e.g. for resource_editing, confirm the user owns or has been granted edit rights on that primitive; for workflow_monitoring, check namespace-level access.

5. Handle Token Expiration Gracefully

Each embed component fires a session-expired event when its token expires. Listen for it, fetch a fresh token from your backend, and update the component's embed-token attribute:

const el = document.querySelector('ss-signing-embed');
el.addEventListener('session-expired', async () => {
  const { token } = await fetch('/api/get-signing-token').then(r => r.json());
  el.setAttribute('embed-token', token);
});

Token Payload Structure

Embed tokens contain specific claims that identify their purpose:

Claim Description
sub Embed session identifier
org_id Organization ID
namespace_key Namespace the token is scoped to
mode live or test
embed_type The embed intent (signing_session, workflow_editing, workflow_monitoring, resource_editing)
workflow_id For workflow intents: the specific workflow this token grants access to
step_key For signing: the participant step
resource_kind / resource_key / resource_version For resource_editing: the primitive being edited (omit resource_version to default to the most recent)
allowed_origins CORS-allowed domains
scopes Permitted actions (e.g., workflow:sign, workflow:edit)
exp Expiration timestamp

Comparison: API Tokens vs. Embed Tokens

Aspect API Access Token Embed Token
Use Case Server-to-server API calls Frontend embedded components
Lifespan 1 hour Configurable, typically 15–60 minutes
Scope Namespace-wide (constrained by Scopes on the API key) A single intent: one workflow + step (signing), one workflow (editing or monitoring), or one primitive version (resource editing)
Generated From API Key (sk_ns_...) via POST /v1/auth/token Access Token via POST /v1/orgs/{orgId}/namespaces/{namespaceKey}/auth/embed
Safe for Frontend? No Yes
Origin Restrictions No Yes (allowedOrigins)

Common Use Cases

Embedded Signing in Your App

Let participants sign documents inline in your application — whether the document is an offer letter, an invoice, an NDA, or anything else:

  1. User initiates signing in your app
  2. Your backend creates a workflow and mints a signing_session embed token
  3. Frontend mounts <ss-signing-embed> with the token
  4. User signs, component fires signed
  5. Your backend receives a webhook notification (authoritative confirmation)

Self-Service Workflow Review

Let users review and edit a workflow's data and documents before launch (or between steps):

  1. User opens the workflow in your app
  2. Backend mints a workflow_editing embed token for that workflow
  3. Frontend mounts <ss-workflow-editor>
  4. User makes changes, saves
  5. Changes are persisted to the workflow

Workflow Status Surface

Show a customer the live status of one of their in-flight workflows — current step, participants, history — without sending them to Studio. They can also nudge a stalled signer or cancel the workflow from the same surface:

  1. User opens a workflow detail view in your app
  2. Backend mints a workflow_monitoring embed token for that specific workflow
  3. Frontend mounts <ss-workflow-monitor>; component shows status, signers, and timestamps in real time, and exposes "Send reminder" per participant and a "Cancel workflow" control

In-App Primitive Editing

Let a customer or an internal team member tweak a primitive that already lives in the namespace — for example, adjusting the wording or fields on one of their own templates — without sending them to Studio:

  1. User clicks "Edit template" in your app
  2. Backend mints a resource_editing embed token for that primitive + version
  3. Frontend mounts <ss-resource-editor>; user edits in your shell, then saves a new draft or publishes a new version

Troubleshooting

Token Rejected (401)

  • Check that the token hasn't expired
  • Verify allowedOrigins includes your domain
  • Ensure the workflow / resource still exists and is accessible

CORS Errors

  • Add your domain to allowedOrigins when generating the token
  • Include both production and staging domains if needed

Component Not Loading

  • Verify the token was generated for the correct intent (each web component requires a matching intent)
  • Check browser console for JavaScript errors
  • Ensure the SignStack web components script is loaded — see Embed SignStack into Your App