Embed Integration Guide

Use this guide to choose the right embed flow, wire up the iframe, and handle signing events safely. For the exact API operations used to create documents or enable direct links, use the API Reference.

Choose the right embed flow

Papyrus supports two different embed patterns.

Flow Best for How it starts
Signing token embed Your app already knows who the signer is Create a document from a template and use the returned embedUrl
Direct link embed Self-serve public flows like waivers or onboarding packets Enable a template direct link and embed /embed/d/TOKEN

If you need multiple signers, custom recipient mapping, or deeper server-side control, use a signing token embed.

Flow 1: Signing token embeds

This is the most common API-driven integration.

  1. Build a template with the correct roles and optional preFillKey values
  2. Create a document from that template with sendEmails: false
  3. Save the Papyrus documentId and your own externalId
  4. Render the returned embedUrl for the signer
  5. Listen for postMessage events from the iframe
  6. Update your backend from webhooks or follow-up API reads

Minimal create request

preFillValues keys must match the template field's preFillKey, not the field label shown in the editor.

curl -X POST "https://your-app.test/api/v1/acme-corp/templates/12/documents" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Contract",
    "signingOrder": "sequential",
    "recipients": [
      {
        "roleLabel": "Signer",
        "name": "Jane Doe",
        "email": "jane@example.com"
      }
    ],
    "preFillValues": {
      "company_name": "Acme Inc."
    },
    "sendEmails": false,
    "externalId": "order-456"
  }'

The 201 Created response includes the signer-specific URLs you need:

{
    "id": 42,
    "status": "sent",
    "externalId": "order-456",
    "recipients": [
        {
            "id": 101,
            "roleLabel": "Signer",
            "type": "signer",
            "status": "sent",
            "signingUrl": "https://your-app.test/sign/abc123...",
            "embedUrl": "https://your-app.test/embed/sign/abc123..."
        }
    ]
}

Note: Save signingUrl and embedUrl when you create the document, but if your process loses them you can fetch the document later with include=recipients to recover the current recipient links. In sequential flows, later signers will not receive a live signingUrl or embedUrl until Papyrus advances the document to their turn.

Direct links let a signer open a template-driven flow without you creating the document first.

Use this when:

  • The same template is reused many times
  • The signer enters their own name and email
  • You want a shareable or embeddable self-serve experience

Typical sequence

  1. Create a template where the direct-link signer is the only signing role
  2. Enable a direct link from the template detail page or the API
  3. Share the standalone link or embed the /embed/d/TOKEN version
  4. Let Papyrus create a new document for each signing session

If your account includes embedded signing features, you can manage direct links through the API Reference as well. If that feature is not enabled for your team, direct-link API operations will not be available.

Iframe examples

Basic iframe

<iframe
    src="https://your-app.test/embed/sign/TOKEN"
    width="100%"
    height="800"
    allow="camera"
    style="border: 0;"
></iframe>

Responsive container

<div style="position: relative; width: 100%; padding-bottom: 75%;">
    <iframe
        src="https://your-app.test/embed/sign/TOKEN"
        allow="camera"
        style="position: absolute; inset: 0; width: 100%; height: 100%; border: 0;"
    ></iframe>
</div>

Handling postMessage events

Papyrus sends embed events to the parent window using the papyrus:signing.* namespace.

window.addEventListener('message', (event) => {
    if (event.origin !== 'https://your-app.test') {
        return;
    }

    switch (event.data.event) {
        case 'papyrus:signing.ready':
            console.log('Embed is ready');
            break;

        case 'papyrus:signing.completed':
            console.log('Signed document', {
                documentId: event.data.documentId,
                documentName: event.data.documentName,
                recipientId: event.data.recipientId,
                signedAt: event.data.signedAt,
            });
            break;

        case 'papyrus:signing.expired':
            console.log('Signing link expired');
            break;

        case 'papyrus:signing.voided':
            console.log('Document was voided');
            break;

        case 'papyrus:signing.error':
            console.log('Signing error', event.data.error);
            break;
    }
});

Event reference

Event Description
papyrus:signing.ready The signing UI loaded and is ready
papyrus:signing.completed The recipient finished signing; payload includes documentId, recipientId, documentName, and signedAt
papyrus:signing.expired The signing link can no longer be used
papyrus:signing.voided The sender voided the document
papyrus:signing.error The signer hit a validation or submission problem
papyrus:signing.field.signed A field was filled or signed
papyrus:signing.field.unsigned A field was cleared

Use these events to update your frontend quickly, but treat your backend as the source of truth. For durable system updates, rely on Webhooks.

Useful URL parameters

These parameters help you pre-fill or lock signer identity for /embed/d/TOKEN flows:

Parameter Description
name Pre-fill the signer's name
email Pre-fill the signer's email
lockName=true Make the name field read-only
lockEmail=true Make the email field read-only
externalId Attach your own reference when the document is created

Both embed types

Parameter Description
redirectUrl Redirect after signing completes. Must match an allowed embed origin
theme_primary Override the primary accent color
theme_background Override the background color
theme_radius Override border radius values
darkMode=true Force dark mode
darkMode=false Force light mode

If a value contains #, URL-encode it. For example, #4f46e5 becomes %234f46e5.

For embedded flows, Papyrus only honors redirectUrl when its origin matches one of your configured allowed embed origins.

Production checklist

Configure allowed origins

Papyrus uses Content-Security-Policy: frame-ancestors to control which sites can embed signing pages. In production, add each allowed domain in Settings > Team > Embedded Signing.

If you leave the allowed origins list empty, Papyrus only allows same-origin embedding. Add every external app origin explicitly before going live.

Examples:

https://myapp.com
https://staging.myapp.com
https://localhost:3000

Treat embed URLs as sensitive

  • Signing token URLs are unique per recipient
  • Direct-link URLs stay active until the direct link is disabled
  • Do not log embed tokens in browser consoles or analytics tools
  • Avoid storing raw embed links where unauthorized users can retrieve them

Use webhooks for server-side state

postMessage is ideal for frontend UX. Webhooks are better for:

  • Marking an order or workflow step as complete
  • Sending your own follow-up emails or receipts
  • Reconciling state if the browser closes before your UI updates

Where to go next

Last updated: April 11, 2026