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.
-
Create the envelope with
delivery: "embedded", so recipients are not emailed. -
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 -
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>import { BonaFideSigning } from '@bona-fide/embed/react';export function SignContract({ token }: { token: string }) {return (<BonaFideSigningsession={token}theme="auto"minHeight={480}maxHeight="90vh"onCompleted={(e) => console.log('envelope completed', e.envelope)}onDeclined={(e) => console.log('declined', e.reason)}onSessionExpiring={() => refreshSession()}onError={(e) => console.error(e.code, e.message)}/>);}<script setup lang="ts">import { BonaFideSigning } from '@bona-fide/embed/vue';defineProps<{ token: string }>();const done = (e: { envelope: string }) => console.log('envelope completed', e.envelope);</script><template><BonaFideSigning :session="token" theme="auto" :min-height="480" max-height="90vh" @completed="done" /></template> -
Confirm on your server. Treat the browser event as a UI signal and rely on the
envelope.completedwebhook (orGET /v1/envelopes/{id}) for the outcome.
Attributes
Section titled “Attributes”| 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
Section titled “Events”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.
Security model
Section titled “Security model”- The token travels in the URL fragment of the iframe (
#session=…), which never reaches a server or aRefererheader. 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) withreferrerpolicy="strict-origin". - Your page accepts a message only when it comes from the element’s own iframe and from the
base-urlorigin and parses as a namespacedbona-fide.embed/1message. Everything else is ignored. - The signing page posts messages to your exact origin (never
*), and only if that origin is in the session’sallowedOrigins. The API also refuses an embedded session’s calls from origins that are not allowed.