Skip to content

Embedded signing

<bf-signing> renders Bona Fide’s signing page in a sandboxed iframe inside your page, sizes it to the document, and reports what the signer does as DOM events. It is framework-free; thin React and Vue wrappers are included.

  1. Create the envelope with delivery: "embedded", so recipients are not emailed.

  2. Mint a session on your server when the signer opens the page, with your site’s origin in allowedOrigins. The embedded page refuses to talk to any parent page that is not listed there.

    // Your backend (never expose the API key to the browser)
    const res = await fetch(`https://bonafide-api.republic.global/v1/envelopes/${envelopeId}/sessions`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.BONA_FIDE_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ recipient: recipientId, mode: 'embedded', allowedOrigins: ['https://app.example.com'] }),
    });
    const { token } = await res.json(); // 'bfs_…', single-use, 10 minutes by default
  3. Render the element with the token.

    <script type="module" src="https://js.bonafidesign.com/v1"></script>
    <bf-signing session="bfs_…" theme="auto" min-height="480" max-height="90vh"></bf-signing>
    <script>
    const el = document.querySelector('bf-signing');
    el.addEventListener('completed', (e) => {
    // e.detail = { type: 'completed', envelope: 'env_…' }
    window.location.href = '/onboarding/next';
    });
    el.addEventListener('session_expiring', () => refreshSession());
    el.addEventListener('error', (e) => console.error(e.detail.code, e.detail.message));
    </script>
  4. Confirm on your server. Treat the browser event as a UI signal and rely on the envelope.completed webhook (or GET /v1/envelopes/{id}) for the outcome.

Attribute (React / Vue prop) Default Notes
session The bfs_… token. Changing it reloads the frame
base-url (baseUrl) https://sign.bonafidesign.com/s/ The hosted signing route of the deployment
theme auto auto, light or dark
locale Passed to the signing page
min-height, max-height (minHeight, maxHeight) 480, 90vh Pixels or vh. The frame grows with the document between the two, then scrolls inside
timeout 30000 Milliseconds to wait for the page before an error with code load_timeout
frame-title Document signing The iframe’s accessible title

The element also has a reload() method, for example after minting a fresh session.

Events bubble, cross shadow roots, and carry the payload in event.detail. bf-event fires for all of them.

Event detail
ready { type: 'ready' }: the signing page loaded
field_completed { type, field }: a field was filled
signed { type, recipient }: this recipient submitted
completed { type, envelope }: the envelope is complete
declined { type, reason? }
session_expiring { type, expiresAt }: mint a new session soon
error { type, code, message }, including load_timeout and invalid_base_url

React props are onReady, onFieldCompleted, onSigned, onCompleted, onDeclined, onSessionExpiring, onError and onEvent; Vue emits the event names (@completed, @session_expiring, …) and event.

  • The token travels in the URL fragment of the iframe (#session=…), which never reaches a server or a Referer header. The signing page reads it, removes it from the URL and history, and exchanges it for an in-memory access token. No cookies are used.
  • The iframe is sandboxed (allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads) with referrerpolicy="strict-origin".
  • Your page accepts a message only when it comes from the element’s own iframe and from the base-url origin and parses as a namespaced bona-fide.embed/1 message. Everything else is ignored.
  • The signing page posts messages to your exact origin (never *), and only if that origin is in the session’s allowedOrigins. The API also refuses an embedded session’s calls from origins that are not allowed.