Skip to content
zSign Help Center home

Embedding signing in your own app

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.com does not cover https://www.app.example.com.
  • https is 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:

EventCallbackFires when
zsign:readyonReadySession loaded and your origin was allowed
zsign:signedonSignedThe 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.

Two callbacks exist in the SDK but never fire: onError and onDeclined. The signing page emits only ready, signed and resize — there is no code path that sends zsign:error or zsign:declined, so a handler attached to either waits forever. Do not build a failure path or a decline path around them.

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 rather than waiting on onError.

Messages from any other origin or window are ignored, so a stray listener elsewhere on your page cannot spoof a completion.