Issue a per-user leaf certificate, sign the PDF ByteRange digest server-side, embed in a CMS PKCS#7 (PAdES-B-B, or PAdES-B-T when TSA_URL is configured)

After /sign and after the browser has APPENDED an incremental update to the locally stamped PDF (new /Sig + Widget + re-emitted Page/Catalog + new xref pointing at /Prev, with placeholder /ByteRange and /Contents <hex>), the browser POSTs the SHA-256 of the PDF's ByteRange plus the verified signer email. The Worker verifies the email matches the envelope-scoped HMAC stored at /sign time, generates an ephemeral ECDSA P-256 keypair, issues a 10-year leaf certificate under the FreeSign CA (Google Cloud HSM in production; the long validity is required because Adobe Reader re-validates the signer cert against the current wall clock when the chain is not AATL-trusted, the ephemeral signing key itself is still destroyed inside the same request) with Subject CN = the human signer's typed name and subjectAltName.rfc822Name = the verified email, signs SignedAttributes with the ephemeral key, and assembles a CMS SignedData with both the leaf and CA certs in the certs set. When env.TSA_URL is configured, the Worker additionally fetches an RFC 3161 TimeStampToken over the signature value and embeds it as the SignerInfo signatureTimeStampToken unsigned attribute (PAdES-B-T). The ephemeral private key never leaves the Worker. The browser embeds the resulting CMS bytes into the /Contents placeholder. PDF bytes are never accepted on this endpoint. The base PDF stays intact as revision 1, the signature is revision 2 — multi-signer documents stack additional revisions cleanly, each with its own per-user leaf cert. Idempotent: same byterange_sha256 on retry returns the cached CMS (and the previously-issued leaf cert); a different byterange_sha256 returns 409 already_sealed_with_different_byterange. Requires the four session-binding headers (see x-fsig-session-* component parameters). FreeSign also submits the byterange hash to OpenTimestamps for independent timestamp proof; after calendar upgrade this proof resolves to public block headers.

post/api/envelopes/{envelope_id}/seal

Request

  • Base URL: https://free-sign.com
  • URL: https://free-sign.com/api/envelopes/{envelope_id}/seal
  • Auth: none declared. Required headers: x-fsig-session-signature, x-fsig-session-nonce, x-fsig-session-timestamp, x-fsig-session-action

Path parameters

envelope_idstring required

Headers

x-fsig-session-signaturestring required

Base64url ECDSA P-256 signature over canonicalJson({action, envelope_id, nonce, timestamp}). Produced by the non-extractable session keypair bound to this envelope at creation. See invariant #11 in CLAUDE.md.

x-fsig-session-noncestring required

Random 32-hex-char nonce, fresh per request.

x-fsig-session-timestampstring date-time required

ISO-8601 UTC timestamp of the request. Accepted within +/-5 minutes of server clock.

x-fsig-session-action'otp.request' | 'otp.verify' | 'sign' | 'seal' | 'platform-seal' | 'finalize' | 'webauthn.register' | 'webauthn.authenticate' required

Action name matching the route. Must equal the action field in the signed canonical payload. The webauthn.* actions gate the browser-only passkey endpoints.

Request body

signer_idstring required
signer_emailstring email required

The signer's email — must match the envelope-scoped HMAC stored at /sign time. Used as the leaf cert's subjectAltName.rfc822Name.

byterange_sha256string required

SHA-256 of (preparedPdf[0..ByteRange[1]) ++ preparedPdf[ByteRange[2]..ByteRange[2]+ByteRange[3])). I.e., everything except the hex chars inside /Contents <...>.

evidence_jsonobject

Optional. The signer's pre-seal evidence record (signer identity, OTP, consent, the signed canonical_payload + its signature + the browser public_key_jwk). The Worker embeds it verbatim into this signer's CMS as a signedAttribute (OID 1.3.6.1.4.1.65834.1.2), so editing the record in the PDF invalidates the CMS signature, so every signer of a multi-signer PDF carries their own. Dropped silently if it is not a plain object, claims a different envelope_id, or stringifies past ~12 KB; the seal still succeeds.

Response

Per-user CMS produced (or returned from cache if idempotent). Response includes the base64 CMS, the leaf cert + serial + validity, and audit hashes.

envelope_idstring required
cms_base64string required

Base64-encoded CMS DER. Caller writes hex(cms_base64-decoded) into the /Contents <...> placeholder, padding the rest with '0'.

seal_cms_sha256string required
seal_cert_sha256string required

SHA-256 of the issued LEAF cert (i.e. the per-user cert, not the CA).

seal_signed_attrs_sha256string
seal_byterange_sha256string
seal_signed_atstring date-time required
signer_cert_base64string nullable

Base64-encoded DER of the issued leaf cert. Carries Subject CN = signer's name and subjectAltName.rfc822Name = signer's email.

signer_cert_serial_hexstring nullable

16-byte serial of the issued leaf cert (hex).

signer_cert_not_afterstring date-time nullable

ISO timestamp at which the leaf cert expires (default 10 years after issuance; the ephemeral signing key is destroyed earlier, the cert outlives it so Adobe's wall-clock revalidation stays green).

seal_ca_mode'local-pem' | 'gcp-kms'

Which Signing-CA implementation produced this signature.

seal_tst_sha256string nullable

SHA-256 of the embedded RFC 3161 TimeStampToken bytes. Null when no TSA was configured.

seal_tst_base64string nullable

Base64 of the raw TimeStampToken (ContentInfo DER) for PDF-free re-verification.

seal_tst_signed_atstring date-time nullable

ISO timestamp at which the Worker accepted the TSA reply. Distinct from the TST's own genTime, which is signed by the TSA.

seal_profile'PAdES-B-B' | 'PAdES-B-T' required

CMS-level profile. The browser upgrades the final PDF to PAdES-B-LT when dss is non-null by appending a /DSS revision.

idempotentboolean

True when the response was served from the cached CMS (same byterange digest replayed).

ots_anchorsobject[]

Both anchors above, in one array (kind=byterange first). Prefer this when iterating; ots_anchor / ots_anchor_signed_attrs are the stable named handles.

Changes

No recorded changes to this operation across all 1 revision of this API.