Embedding signing in your own app
In this article
You can host the signing experience inside your own product instead of sending people to zSign. Two packages:
@zsign/embed— vanilla JS. Requires Node 18+.@zsign/react— React wrapper around the same thing. Requires React 18+.
Both are thin parents around zSign's signing iframe. The page inside the frame is still zSign's signing UI; the packages mount it and relay events.
Allow your origin first — this is the step everyone misses
Embedding is default-deny. With an empty origin list, every attempt to frame the signing page is blocked. If your integration shows a "blocked frame" screen instead of the document, this is why, and it is not a bug in your code.
Register the exact origin of the hosting page under Settings → Embedding, or via PUT /api/branding/embed-origins.
The rules are strict:
- Exact match only — no wildcards, no paths.
https://app.example.comdoes not coverhttps://www.app.example.com. httpsis required, with one exception:http://localhost(with or without a port) for local development.- At most 10 origins. Hosts are stored lowercased.
Register your staging and production origins separately. They are different origins.
Getting a signing URL
Use the signing_urls returned by POST /api/v1/documents/send, or copy a recipient link from the dashboard. There is one link per signer.
If you are delivering that link yourself — showing it in your own app — pass send_invite: false on send, and zSign will not email it as well. Leave it out and your signer gets both.
Events
The signing page posts messages to your page, which the SDK surfaces as callbacks:
| Event | Callback | Fires when |
|---|---|---|
zsign:ready | onReady | Session loaded and your origin was allowed |
zsign:signed | onSigned | The signer completed the document |
zsign:resize | (iframe height) | Body resized — used when height="auto" |
Set height="auto" and the frame resizes itself as the document renders.
The SDK callbacks are onReady and onSigned only. There is no onError and no onDeclined. The signing page emits ready, signed, and resize — it does not send zsign:error or zsign:declined.
Use webhooks for both cases instead (see article 9): document.declined tells you a recipient refused, and the absence of document.completed is what tells you a session did not finish. If you need a client-side timeout, implement it in your own host page.
Messages from any other origin or window are ignored, so a stray listener elsewhere on your page cannot spoof a completion.