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.

Frontend Pages: SigningPage, SignatureDashboard, SignatureRequestDetail, SendForSignature, EnvelopeDetail
Key Components: PdfFieldRenderer, SignatureModal, ReviewConsentModal, FinalConsentModal, DeclineModal, SigningCompletion
Backend Functions: validateSigningSession, submitSignatureAction, ensureSignerDigitalId, generateAuditCertificate, appendSignatureCertificate
Core Entities: Envelope, Signer, SignatureField, EnvelopeDocument, SigningSession

End-to-End Signing Flow

1

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)
2

Envelope is Sent

  • Backend function sendEnvelope creates 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 ensureSignerDigitalId assigns a permanent 6-char Digital ID to each signer email
3

Signer Opens Link → SigningPage

  • URL params: session, envelope, signer (new format) or token (legacy)
  • Theme via ?theme=light|dark for embedded/iframe use
  • Demo mode: ?demo=true skips all API calls for preview
4

Session Validation

  • validateSigningSession verifies 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
5

Document Rendered → PdfFieldRenderer

  • Uses pdfjs-dist to 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
6

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
7

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
8

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
9

Final Consent → FinalConsentModal

  • Shown when all required fields are completed
  • Final ESIGN/UETA confirmation: audit trail, timestamping, data retention
  • Checkbox required → Submit button enabled
10

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
11

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.

ENCODE_MAP:{ '0': '$', '1': 'A', '2': 'B', '3': 'C', '4': 'D', '5': 'E', '6': 'F', '7': 'G', '8': 'H', '9': 'I' }
Format:DID-DOCID-MMDDYY
DID = 6-char signer digital ID (permanent, assigned per email)
DOCID = 6 random alphanumeric chars (document/certificate identifier)
MMDDYY = encoded date, 6 chars (e.g. 061826 → $FAHB$)
Example: K9IYLP-7X3MNQ-$FAHB$ = June 18, 2026

Certificate Stamp Display

Rendered below each filled signature/initials field in the PdfFieldRenderer.

CID:{DID}-{DOCID}-{MMDDYY} · MM/DD/YYYY HH:MM

Example: 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.

Page 1 of 3
Jane Doe
CID:K9IYLP-7X3MNQ-$FAHB$·06/18/2026 15:42
CID-K9IYLP-7X3MNQ-$FAHB$·06/18/2026 15:42·ClosedInk

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 / 2

Field 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 * filledExpand

Signer 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.

Signer 1
Blue
Signer 2
Green
Signer 3
Purple
Signer 4
Amber
Signer 5
Red

Modes

Interactive (Editor): Fields are draggable, resizable, selectable. Supports multi-select (Shift+click or toggle), snap-to-grid, bulk delete. New fields added by selecting type and clicking canvas.
Signing Mode: Read-only view with active field highlighting (white pulse ring). Tap-to-fill. Auto-scrolls to active field. Progress dots at bottom. Only one active field at a time.

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.
sessionToken
envelopeId
signerId

Returns: 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.
sessionToken
envelopeId
signerId
action"sign" or "decline"
signatureType"drawn" or "typed"
signatureUrl
certificateId
fieldValues[{field_id, filled_value}]
ipAddress
timezone
userAgent

Returns: 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 process
ownerEmailEnvelope owner email for sender DID
generateAuditCertificate— Creates a PDF audit certificate page with signing metadata, timestamps, IP addresses, and certificate IDs.
envelopeId
signerId
appendSignatureCertificate— Merges the audit certificate page into the completed signed document PDF.
documentUrl
certificateUrl
generateEmbeddedSigningSession— Creates a signing session for iframe embedding, generating a session token and returning the signing URL.
envelopeId
signerId
returnUrl
sendEnvelope— Sends an envelope to all signers: creates signing sessions, emails links, triggers webhooks and workflows.
envelopeId
emailSigningLink— Sends the signing invitation email to a specific signer via the Gmail connector.
signerId
sessionToken
envelopeId
createEnvelope— 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/testing

Signature Field Types

✍️signature

Primary signature capture — draw or type

Default: 37×7%

Iinitials

Initials — same capture as signature

Default: 8×4%

📅date

Auto-populated on adjacent signature completion

Default: 14×3.5%

Ttext

Free text input field

Default: 20×3.5%

☑checkbox

Binary check/uncheck

Default: 4×4%

@email

Email address input

Default: 22×3.5%