Skip to content
On this page

Developers

Markdown

Embedded signing

Let recipients sign inside your app or on an eSignStack page, using short-lived signing sessions your server creates through the API.

Your server creates a signing session for one recipient and gets back a single-use URL. You either load it in an iframe (embedded) or send the browser to it (hosted). eSignStack runs the signing page in both cases, including consent and any required authentication.

Before you start

  • Get an API key with envelopes:write. See the API quick start.
  • In Settings → Embedding, add each origin that will frame the signing page, such as https://app.example.com. With no origins, embedding is off.
  • On the same page, add Allowed return origins if you want people sent back to your app afterward. Use one exact origin per line, with no path or wildcard.
  • Send the envelope. A session can only be created for a recipient whose turn it is. To keep eSignStack from emailing signing links to anyone, create the envelope with send_emails: false. To hold them back for some recipients only, set embedded: true on those recipients; the others still sign from their email.

Create a signing session

Call this from your server:

bash
curl -X POST https://esignstack.app/api/v1/envelopes/$ENVELOPE_ID/recipients/$RECIPIENT_ID/signing_sessions \
  -H "Authorization: Bearer $ESIGNSTACK_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-456-sign-1" \
  -d '{
    "mode": "embedded",
    "parent_origin": "https://app.example.com",
    "return_url": "https://app.example.com/orders/456"
  }'
Field Notes
mode embedded or hosted
parent_origin Required for embedded. Must exactly match an origin in Settings → Embedding.
return_url Optional. Must be on an allowed return origin.
appearance Optional theme on top of your branding: primary (#rrggbb), radius (0px to 24px), font (instrument-sans, system or serif), dark_mode
actor Optional name, email, external_id and auth_method of the person your app signed in. Recorded in the envelope history.

The response includes id, url and expires_at. The URL works once and must be opened within 10 minutes. After it opens, the session ends after 60 minutes without activity or 8 hours at most. Create a new session (with a new idempotency key) whenever you need another link. Don't store signing URLs or write them to logs.

The recipient still has to pass any authentication set on them, such as an access code or SMS code.

Embed the signing page

Set the iframe's src to the session URL without changing it:

js
const frame = document.createElement('iframe');
frame.src = session.url;
frame.title = 'Sign your agreement';
frame.style.width = '100%';
frame.style.height = '800px';
frame.style.border = '0';
document.querySelector('#signing').append(frame);

The page sets frame-ancestors to your parent_origin, so it can only be framed there.

For hosted, leave out parent_origin and redirect the browser to the URL.

Listen for messages

The embedded page posts messages to parent_origin. Each message has an event key:

event When
esignstack:ready The page loaded
esignstack:page.viewed A page scrolled into view (pageNumber)
esignstack:consent.accepted The recipient agreed to sign electronically
esignstack:field.completed A field was filled in (fieldId, fieldType)
esignstack:recipient.signed The signer finished
esignstack:recipient.approved The approver approved
esignstack:recipient.acknowledged The recipient confirmed they read the documents
esignstack:recipient.declined The recipient declined
esignstack:session.expired The session expired
esignstack:error Something went wrong (error, for example envelope_voided)
esignstack:resize The content height changed (height)

Completion messages include session, envelope, recipient and status. Field values aren't sent unless you turn on Include field values in embed events in Settings → Embedding.

js
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://esignstack.app' || event.source !== frame.contentWindow) return;
    if (event.data?.event === 'esignstack:resize') frame.style.height = `${event.data.height}px`;
});

Return to your app

If you set return_url, the page goes there when the recipient finishes, declines, or the session ends (for an embedded session, inside the iframe). It adds session, envelope, recipient and status (signed, approved, acknowledged, declined, expired or cancelled) as query parameters. Any query string already on return_url is replaced.

Confirm the result on your server

Messages and redirects only update your interface. Before you mark an order signed, confirm with GET /api/v1/envelopes/{envelope} or a verified recipient.signed or envelope.completed webhook. Get the completed PDF from GET /api/v1/envelopes/{envelope}/artifacts.

Usage

Creating a session for an envelope that was sent in the app counts it as an API envelope, once. See API envelope limits.

A direct link can also be framed. Use /embed/d/{token} with the same token as the link's /d/{token} URL, on a page whose origin is in Settings → Embedding. Optional query parameters:

Parameter Does
signerName, signerEmail Prefill the signer's name and email
lockName, lockEmail true stops the signer changing them
externalId Stored as the new envelope's external_id
redirectUrl Where to go afterward. Must be on an allowed return origin.

Common errors

Response Cause
422 on parent_origin The origin isn't listed in Settings → Embedding
422 on return_url The URL isn't on an allowed return origin
403 It isn't this recipient's turn, they've finished, or the envelope isn't out for signature
410 when opening The URL was already used or is more than 10 minutes old

To let your users prepare envelopes in your app as well, see Editor sessions.