Signature System Reference
Complete architecture, process flow, and encoding specification for ClosedInk's electronic signature system.
System Overview
ClosedInk's signature system allows document senders to prepare envelopes with signature fields positioned on PDF pages, send them to signers, and collect legally-binding electronic signatures with verifiable audit trails.
End-to-End Signing Flow
Sender Prepares Envelope
- Upload PDF document(s) to an Envelope
- Add Signers with name, email, role, and signing order
- Place SignatureField records on specific PDF pages with x/y/width/height as percentages
- Fields can be: signature, initials, date, text, checkbox, email, textarea
- Each field links to a signer_order (which signer fills it)
Envelope is Sent
- Backend function
sendEnvelopecreates a SigningSession per signer - Each session has a unique token, expires_at timestamp, and status
- Signing links are emailed via Gmail connector:
/sign?session={token}&envelope={id}&signer={id} - Function
ensureSignerDigitalIdassigns a permanent 6-char Digital ID to each signer email
Signer Opens Link → SigningPage
- URL params: session, envelope, signer (new format) or token (legacy)
- Theme via
?theme=light|darkfor embedded/iframe use - Demo mode:
?demo=trueskips all API calls for preview
Session Validation
validateSigningSessionverifies the token, checks session.status = "active", verifies signer hasn't already signed- Returns: envelope data, document URL, form fields (filtered by signer_order), signer info including digital_id
- Document URLs are resolved to signed/viewable formats
- Error states: "already signed", expired session, not found
Document Rendered → PdfFieldRenderer
- Uses
pdfjs-distto render PDF pages as stacked canvases - Field overlays positioned on top using percentage→pixel coordinate mapping
- Each field rendered as a positioned div with signer-color-coded border/background
- Unfilled fields: visible border + label (Sig, Init, Date, etc.)
- Filled fields: border/background removed, signature content displayed inline with certificate stamp below
Review Consent → ReviewConsentModal
- Shown on first page load (stage = "view")
- Legal disclosure: ESIGN/UETA electronic signature compliance
- Requires checkbox agreement before proceeding to sign mode
- Displays document title, sender name, expiration date, and field count
Signature Capture → SignatureModal
- Two capture modes: Type and Draw
- Type: Enter name → rendered in cursive font (signature, great-vibes, caveat, pacifico). Color selector: black, blue, gray.
- Draw: Canvas-based drawing with touch support. Color selector for pen ink.
- Drawn signatures: canvas → dataUrl → fetch blob → UploadFile integration → stored file_url
- Typed signatures: name string stored directly as filled_value
- Signature cached in localStorage per email (90-day expiry) for reuse across documents
Field Completion & Auto-Advance
- Signature accepted →
handleFieldComplete(index, value) - Sets field.filled_value and field.completed = true
- Auto-fills adjacent date fields (same page, same signer, within 45% horizontally right and 2x field height vertically)
- Certificate ID generated once on first signature capture via
generateCertId(timestamp, signerDigitalId) - Auto-advances to next unfilled signature/initials field
- Progress bar shows sigCompletedCount / sigFields.length
Final Consent → FinalConsentModal
- Shown when all required fields are completed
- Final ESIGN/UETA confirmation: audit trail, timestamping, data retention
- Checkbox required → Submit button enabled
Submission → submitSignatureAction
- Sends: sessionToken, envelopeId, signerId, action="sign", signatureType, signatureUrl, certificateId, fieldValues[], ipAddress, timezone, userAgent
- Backend updates: SignatureField records (filled_value, filled_by, filled_at), Signer record (status="signed", signature_url, ip, etc.)
- Generates audit certificate PDF via
generateAuditCertificate - Appends certificate page to completed document via
appendSignatureCertificate - Returns: completed_document_url, certificate_only_url
Completion → SigningCompletion
- Shows success confirmation with document title and timestamp
- Download options: completed document (with certificate), original document (without certificate)
- Decline flow: signer picks reason (not_authorized, need_changes, not_ready, other) + optional message
Certificate ID & Digital ID Encoding
Digital ID (DID)
A permanent 6-character alphanumeric identifier assigned to every signer email across the platform. Generated by ensureSignerDigitalId using characters A-Z and 0-9. Stored on the Signer entity as digital_id. Once assigned, the same email always gets the same DID.
Certificate ID (CID) Generation
Generated client-side at the moment of first signature capture, encoding the exact timestamp using a digit-substitution cipher.
{ '0': '$', '1': 'A', '2': 'B', '3': 'C', '4': 'D', '5': 'E', '6': 'F', '7': 'G', '8': 'H', '9': 'I' }DID-DOCID-MMDDYYK9IYLP-7X3MNQ-$FAHB$ = June 18, 2026Certificate Stamp Display
Rendered below each filled signature/initials field in the PdfFieldRenderer.
CID:{DID}-{DOCID}-{MMDDYY} · MM/DD/YYYY HH:MMExample: CID:K9IYLP-7X3MNQ-$FAHB$ · 06/18/2026 15:42
DID = signer's 6-char digital ID. DOCID = random 6-char document identifier. MMDDYY = encoded signing date (MMDDYY, only 0→$). The human-readable timestamp follows for the exact time. Colors: dark gray (slate-600/700), no border separator.
Per-signature stamp (shown above): Appears directly below each filled signature/initials field. Includes signer's DID, random document ID, and encoded signing date. The exact time is shown in the human-readable portion. Color: slate-600/700.
Per-page stamp (shown at bottom): Printed at the bottom of every document page. Envelope-level identifier with "ClosedInk" suffix. Color: slate-400.
PdfFieldRenderer Architecture
Coordinate System
Fields store positions as percentages (0-100) of page width/height. The renderer converts percentages to pixels based on the current scale and page dimensions. All positioning is relative to individual PDF pages stacked vertically with a 16px gap.
left = (field.x / 100) * pageRender.width - fieldWidth / 2
top = pageRender.top + (field.y / 100) * pageRender.height - fieldHeight / 2Field Sizing
Signature/initials fields expand 1.25× their defined dimensions for clickability. When filled, expansion reduces to 1.05× (shrink-to-fit). Minimum dimensions enforced: 40×18px filled, 36×14px empty.
filledExpand = isFilled && isSigRelated ? 1.05 : 1.5
fieldW = field.width * 1.25 * filledExpand
fieldH = field.height * 1.25 * filledExpandSigner Color Coding
Each signer_order gets a color from a 5-color palette (mapped via modulo). Used for field borders, backgrounds, and the signer number badge.
Modes
Signature Caching System
Signatures are cached in localStorage so signers don't need to recreate their signature for every document.
{ email: { signatureUrl, signatureType, signatureFont, signatureColor, savedAt } }- Cache key:
closedink_signature_cache - Entries expire after 90 days
- Drawn signatures cache the uploaded file_url
- Typed signatures cache the name string + font selection + color
- Loaded on SigningPage mount if signer email is known
- Saved on every signature acceptance
Backend Function Reference
validateSigningSession— Validates a signing session token and returns envelope/document/field data for the signing UI.sessionTokenenvelopeIdsignerIdReturns: envelope, signer, document (url, page_count, page dimensions), formFields (filtered by signer_order), sender_digital_id
submitSignatureAction— Processes a signature submission: updates fields, signer status, generates audit certificate, appends it to the document.sessionTokenenvelopeIdsignerIdaction"sign" or "decline"signatureType"drawn" or "typed"signatureUrlcertificateIdfieldValues[{field_id, filled_value}]ipAddresstimezoneuserAgentReturns: success, completed_document_url, certificate_only_url
ensureSignerDigitalId— Assigns or retrieves a permanent 6-char digital ID for each signer email. Also handles owner emails for sender DID.emailsArray of signer emails to processownerEmailEnvelope owner email for sender DIDgenerateAuditCertificate— Creates a PDF audit certificate page with signing metadata, timestamps, IP addresses, and certificate IDs.envelopeIdsignerIdappendSignatureCertificate— Merges the audit certificate page into the completed signed document PDF.documentUrlcertificateUrlgenerateEmbeddedSigningSession— Creates a signing session for iframe embedding, generating a session token and returning the signing URL.envelopeIdsignerIdreturnUrlsendEnvelope— Sends an envelope to all signers: creates signing sessions, emails links, triggers webhooks and workflows.envelopeIdemailSigningLink— Sends the signing invitation email to a specific signer via the Gmail connector.signerIdsessionTokenenvelopeIdcreateEnvelope— Creates a new envelope with documents, signers, and signature fields — the entry point for the entire signing workflow.Signing Page URL Parameters
?session=Signing session token (new format)?envelope=Envelope ID (aliases: envelope_id)?signer=Signer ID?token=Legacy base64-encoded token containing signerId and requestId?request_id=Legacy SignatureRequest ID?signer_id=Legacy Signer ID?theme=light or dark — for embedded/iframe use?demo=true — bypasses all API calls for preview/testingSignature Field Types
Primary signature capture — draw or type
Default: 37×7%
Initials — same capture as signature
Default: 8×4%
Auto-populated on adjacent signature completion
Default: 14×3.5%
Free text input field
Default: 20×3.5%
Binary check/uncheck
Default: 4×4%
Email address input
Default: 22×3.5%
