Iframe embed
1. Allow your origin
Add your site's origin — scheme and host, no path — under Allowed embed origins on your application. Two things depend on it:
- The sign page sends a
Content-Security-Policywithframe-ancestorsbuilt from that list. An origin that is not on it cannot frame the page at all — the browser refuses. - Progress events are posted only to those origins. An unexpected embedder learns nothing, not even the submission id.
If the frame renders blank, this is almost always why. Check the browser console for a frame-ancestors violation before looking anywhere else.
2. Render the component
npm install @docbloc/embedimport { DocBlocSignature } from '@docbloc/embed'
<DocBlocSignature
baseUrl="https://sign-docbloc.com"
token={signLink}
style={{ height: '80vh' }}
onOpened={() => setStatus('The signer has the document open')}
onSigned={(e) => setStatus(`Recipient ${e.recipientIndex} signed`)}
onCompleted={() => refreshRecord()}
onDeclined={() => setStatus('The signer declined')}
onError={(e) => showError(e.message)}
/>token accepts either the bare token or the full sign link the workflow returned, because it is easy to pass the link straight through.
3. Do not trust the events for state
The events are for the interface — showing progress, closing a dialog, refreshing a list. They come from a browser the signer controls, so they are not evidence.
Record completion from the webhook, never from onCompleted. The webhook is signed by DocBloc and delivered server to server; the message is not. Use one to update the screen and the other to update the database.
Without the package
DocBlocSignature is a thin wrapper — an <iframe> plus a message listener. If you would rather not take the dependency, or you are not on React, this is the whole thing:
<iframe
src="https://sign-docbloc.com/sign/eyJ..."
style="border: none; width: 100%; height: 80vh"
allow="clipboard-write"
title="Sign document"
></iframe>src is {baseUrl}/sign/{token} — or just the full sign link the workflow returned, since it is already exactly that URL. allow="clipboard-write" is what lets the page capture a drawn signature.
useEffect(() => {
const listener = (event: MessageEvent) => {
// Anyone can post to a window; only DocBloc's own origin is DocBloc.
if (event.origin !== 'https://sign-docbloc.com') return
if (event.data?.source !== 'docbloc') return
handle(event.data)
}
window.addEventListener('message', listener)
return () => window.removeEventListener('message', listener)
}, [])The origin check is the part that matters. Without it any page in the frame tree can send you a message claiming a document was signed.
Events
opened— the signer has the document in front of themsigned— this recipient has signed; carriesrecipientIndexcompleted— every recipient has signeddeclined— a recipient declined; the submission is closederror— something failed; carries amessage
Use onEvent to receive everything, including event types added after the version of the package you are on.
Letting your users place fields: DocBlocConfigurator
A sibling component frames DocBloc's field configurator instead of the sign page, so a user of yours can place signature and merge fields on a template you uploaded — with no DocBloc account. Request a configure link server-to-server with your client credentials (POST /pdfadmin/{id}/configure-link), then frame the token it returns:
import { DocBlocConfigurator } from '@docbloc/embed'
<DocBlocConfigurator
baseUrl="https://sign-docbloc.com"
token={configureLink}
style={{ height: '80vh' }}
onSaved={(e) => storeExpectedFields(e.fields)}
onError={(e) => showError(e.message)}
/>onSaved fires after every save with the field names placed so far — store them against your own document record so you know what values to supply, and can check every field is satisfied before creating submissions from the template. Same allow-listing and same rule as above: these events are for your interface, not your database.
Without the package, it is the same shape as DocBlocSignature above, just with /configure/ in place of /sign/ and a saved event in place of signed/completed:
<iframe
src="https://sign-docbloc.com/configure/eyJ..."
style="border: none; width: 100%; height: 80vh"
title="Configure document"
></iframe>window.addEventListener('message', (event) => {
if (event.origin !== 'https://sign-docbloc.com') return
if (event.data?.source !== 'docbloc' || event.data.type !== 'saved') return
storeExpectedFields(event.data.fields)
})Minting the configure link
Frame a token you already have, not a template id — request it server-to-server with your own credentials, scoped to one template and expiring on its own schedule (default 60 minutes, up to 24 hours):
POST /pdfadmin/{id}/configure-link
Content-Type: application/json
{ "expiryInMinutes": 60 }
200 OK
{ "data": { "token": "eyJ...", "url": "https://sign-docbloc.com/configure/eyJ..." } }Anyone holding the token can lay out fields on that one template until it expires — it needs no DocBloc account of its own, so treat it the way you would a signed URL: mint it just before handing it to the person who will use it, not far ahead of time.