Skip to content

Signing sessions & links

Recipients sign on Bona Fide’s signing page: either the hosted page at https://sign.bonafidesign.com/s/…, or the same page embedded in your product with <bf-signing>. Recipients do not need an account. What lets them in is a signing session.

Email delivery creates sessions for you (the link in the email). For embedded or self-delivered signing, mint one from your backend:

Terminal window
curl https://bonafide-api.republic.global/v1/envelopes/env_8Gm2kQ4rT1/sessions \
-H "Authorization: Bearer $BONA_FIDE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "recipient": "rcp_5Jw0pLs9Xe", "mode": "embedded", "allowedOrigins": ["https://app.example.com"], "ttlSeconds": 600 }'
{
"id": "ses_2Hq8vNw3Kd",
"token": "bfs_…",
"url": "https://bonafide-app.republic.global/s/?embed=1#session=bfs_…",
"expiresAt": "2026-10-13T14:08:04.512Z"
}
Field Notes
recipient A recipient of the envelope who can still sign (not CC, not finished)
mode embedded (default) or hosted. Hosted urls are …/s/<token>, to redirect to or send yourself
allowedOrigins Up to 10 origins that may frame the embedded page
returnUrl Where the hosted page sends the signer when they are done
ttlSeconds 60 to 3600, default 600

Sessions are single-use: once the recipient submits, the session is spent, and a used or expired session answers 410 Gone. Mint a fresh one each time your page renders. Sessions can only be created while the envelope is sent, and voiding the envelope ends them all.

The signing page (and any custom UI you build on the same API) follows these steps:

  1. Exchange: POST /v1/signing/exchange { "token": "bfs_…" } returns a short-lived access token (bfa_…) kept in memory. No cookies are involved, so third-party cookie blocking does not matter in an iframe.
  2. Load: GET /v1/signing/session returns the signer view (envelope title, sender, the recipient’s role and fields, prefilled values, the consumer disclosure) and records recipient.viewed the first time. GET /v1/signing/document is the PDF to display.
  3. Verify: with email_otp, POST /v1/signing/otp emails a six-digit code (10 minutes) and POST /v1/signing/otp/verify checks it. A wrong code returns verified: false with the attempts left.
  4. Submit: POST /v1/signing/submit with the field values (signatures drawn, typed or uploaded), consent to the disclosure, and viewedDocumentHash: the SHA-256 of the exact PDF that was shown. If the document changed since, the submit is refused (409).
  5. Or decline: POST /v1/signing/decline { "reason": "…" } ends the envelope (envelope.declined).

It is the recipient’s turn only when every earlier routing group is done; before that the view has canSign: false, and a submit answers 409 not-your-turn.

A signing link is a permanent URL for contracts that many people sign the same way: a waiver, a contractor agreement, terms for a pilot. Every visitor who completes it gets their own envelope.

Terminal window
curl https://bonafide-api.republic.global/v1/signing-links \
-H "Authorization: Bearer $BONA_FIDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "tpl_…",
"visitorRole": "contractor",
"countersign": { "role": "company", "mode": "auto" },
"limits": { "perEmailPerDay": 3, "perIpPerHour": 20, "dailyCap": 200, "allowedEmailDomains": ["example.com"] }
}'
  • visitorRole is the signer role the visitor takes. Links support one visitor and at most one countersigner: mode: "auto" countersigns automatically with the Bona Fide platform seal, mode: "person" routes the envelope to the given email. Every other non-CC role would have no recipient, so such templates are refused.
  • The visitor enters their name, email and the template’s variables, then confirms an email code before anything is created (POST /v1/links/{id}/start, then POST /v1/links/{id}/verify).
  • limits caps use per email per day (default 3), per IP per hour (default 20), per day overall, and by email domain. With dedupe (default on), an email address that already signed gets 409 already-signed.
  • PATCH /v1/signing-links/{id} pauses (active: false), resumes or moves the link to a newer version; deleting a link keeps the envelopes it created. Each use emits signing_link.used.