Embedded signing
Your customer signs inside your app instead of following an email link. They still go through every step an emailed signer does.
Status. The SignWren API is opening in stages and may not be on your account yet. If Settings shows an API keys section, you can make sandbox keys now; live keys come with the Pro and Business plans. SignWren is an electronic signature app for small businesses; it is not a law firm and does not give legal advice.
How does embedded signing work?
You add the signer with embedded: true, send the envelope, and when the person is in your app you ask SignWren for a signing URL that lasts 5 minutes and works in one browser. You show that URL in an iframe on a site you allowed in Settings, or send the person to it and get them back with return_url. SignWren emails embedded signers no signing link or reminder.
The signer still sees the notice about electronic records, gives consent and signs in the SignWren signing ceremony. Your app can't sign for them, fill in their fields or tick the consent box; the URL only opens the ceremony.
What does it look like in code?
First add the sites your app runs on in Settings, under Embedding, separately for sandbox and live. Then create and send the envelope, and ask for a URL when the signer is ready:
const envelope = await sw.envelopes.create({
title: 'Lease, 14 Alder Court',
document_ids: [doc.id],
recipients: [
{ name: 'Priya Raman', email: 'priya@example.com', role: 'signer', embedded: true, client_user_id: 'tenant-4471' },
],
fields: [{ document_id: doc.id, recipient_index: 0, type: 'signature', page: 2, x: 72, y: 600, width: 180, height: 40 }],
});
await sw.envelopes.send(envelope.id);
// When Priya is in your app and ready to sign (ask for a new URL every time):
const { url } = await sw.envelopes.recipients.signingUrl(envelope.id, envelope.recipients[0].id, {
frame_origin: 'https://app.example.com',
});Put the URL in an iframe and listen for messages from it. Check the origin and the source before you read anything, and confirm with the API or a webhook before you act:
<iframe id="signwren" src="SIGNING_URL" title="Sign the lease"></iframe>
<script>
const frame = document.getElementById('signwren');
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.signwren.com' || event.source !== frame.contentWindow) return;
const msg = event.data;
if (msg?.source !== 'signwren' || msg.version !== 1) return;
if (msg.type === 'signwren:signed') refreshFromYourServer(msg.envelope_id); // confirm with the API
});
</script>What exactly does the API promise?
This section is the Embedded part of the API specification, shown as written:
Signing and sending inside your own app (P3-EMB). Add the sites your app runs on in Settings, under Embedding (separately for live and sandbox). Then ask for a short-lived URL and either put it in an iframe on one of those sites or send the person to it.
Signing. Add the signer with embedded: true and your own client_user_id for them. SignWren sends them no signing email or reminder (they still get the completed copy). Once the envelope is sent and it is their turn, POST /envelopes/{envelope_id}/recipients/{recipient_id}/signing_url gives you a URL that must be opened within 5 minutes and then works for that one browser only. Ask for a new one each time the person signs; the newest URL stops the older ones. The signer sees the same notice about electronic records, consent step and signing steps as an emailed signer. The certificate shows "Embedded, identified by <your business name> as <client_user_id>".
Framing. The page may be framed only by the site the URL was made for (frame_origin), enforced with Content-Security-Policy: frame-ancestors. Everything else is refused by the browser.
Events. The page posts messages to window.parent only, with targetOrigin set to the URL's frame_origin (never *). Check event.origin equals the SignWren origin you framed and event.source is your iframe before you read event.data. Each message is: { "source": "signwren", "version": 1, "type": "<type>", "envelope_id": "env_..." | null, "recipient_id": "rcp_..." | null, "status": "<status>" | null, "code": "<code>" | null } with type one of signwren:ready, signwren:signed, signwren:declined, signwren:withdrew_consent, signwren:error (with code: link_expired, link_used, unavailable or cookies_blocked) and, from a sending view, signwren:sent. Messages never carry names, emails, field values or document content. Treat them as hints and confirm with GET /envelopes/{envelope_id} or a webhook before you act on them.
Redirect instead of a frame. Give return_url (its origin must be an allowed site). When the signer finishes, declines or asks for paper they are sent there with ?envelope_id=&event= (signed, declined, withdrew_consent).
Web component. Instead of your own iframe and message listener, load <script src="https://app.signwren.com/embedded/v1/signwren.js"></script> and use <signwren-signing url="..."> (or <signwren-sending url="...">). It frames only a SignWren URL of its own origin, checks every message, and fires each one as a DOM event of the same name (signwren:signed, ...) with the message as event.detail.
Sending. POST /envelopes/{envelope_id}/sender_view_url gives a 5-minute, one-browser URL to the field editor for that draft only (no dashboard, no other envelopes). Pressing Send there posts signwren:sent.
Sandbox. Sandbox URLs work on every plan, are marked as tests on every page and in the finished PDF, and never count toward your plan. For the sandbox, http://localhost sites are allowed too.
The two endpoints, createSigningUrl and createSenderViewUrl, are in the API reference.
Last checked against the API spec on .