signingLast updated on
Last updated on
Signers, signature capture, witnesses, and attestations
The signing flow in Paradoc separates the concepts of who can sign (Signers) from who they sign for (Parties). This enables scenarios where one person signs for multiple parties, or an authorized representative signs on behalf of an organization.
Signing Flow Overview
// 1. Fill form with party data
const draft = form.fill({
fields: { /* ... */ },
parties: {
landlord: { id: 'landlord-0', name: 'ABC Property LLC' },
tenant: { id: 'tenant-0', name: 'Jane Smith' }
}
})
// 2. Register signers with their adopted signatures
const withSigners = draft
.addSigner('jane', {
person: { name: 'Jane Smith' },
adopted: {
signature: { image: 'data:image/png;base64,...', method: 'drawn' },
initials: { method: 'typed' }
}
})
.addSigner('bob', {
person: { name: 'Bob Manager' },
adopted: {
signature: { image: 'data:image/png;base64,...', method: 'drawn' }
}
})
// 3. Link signers to parties (who signs for whom)
const ready = withSigners
.addSignatory('tenant', 'tenant-0', { signerId: 'jane' })
.addSignatory('landlord', 'landlord-0', { signerId: 'bob', capacity: 'Managing Member' })
// 4. Prepare for signing (freezes data)
const signable = ready.prepareForSigning()
// 5. Capture signatures at template locations
const signed = signable
.captureSignature('tenant', 'tenant-0', 'jane', 'tenant-final-sig')
.captureSignature('landlord', 'landlord-0', 'bob', 'landlord-final-sig')
// 6. Add witnesses and attestations
const witnessed = signed
.addWitness({ id: 'witness-0', party: { name: 'Sam Witness' } })
.addAttestation({
witnessId: 'witness-0',
signature: { image: 'data:...', method: 'drawn', timestamp: new Date().toISOString() },
attestsTo: [
{ role: 'tenant', partyId: 'tenant-0', signerId: 'jane' }
]
})
// 7. Finalize
const executed = witnessed.finalize()Signer
A Signer represents a person who can sign documents, along with their adopted signature and initials.
interface Signer {
person: Person // Who is signing (always a Person)
adopted?: {
signature?: AdoptedSignature // Adopted signature image
initials?: AdoptedSignature // Adopted initials image
}
}AdoptedSignature
An AdoptedSignature is a reusable signature or initials image that a signer has approved.
interface AdoptedSignature {
image?: string // Base64-encoded image or data URI
method: 'drawn' | 'typed' | 'uploaded' | 'certificate'
}PartySignatory
A PartySignatory links a signer to a party, specifying who signs for whom.
interface PartySignatory {
signerId: string // Reference to a Signer
capacity?: string // Role or title (CEO, Managing Member, etc.)
}Example: One Signer, Multiple Parties
// Jane signs for herself as tenant AND as authorized representative for guarantor
const draft = form.fill({
parties: {
tenant: { id: 'tenant-0', name: 'Jane Smith' },
guarantor: { id: 'guarantor-0', name: 'Smith Family Trust' }
}
})
.addSigner('jane', { person: { name: 'Jane Smith' }, adopted: { /* ... */ } })
.addSignatory('tenant', 'tenant-0', { signerId: 'jane' })
.addSignatory('guarantor', 'guarantor-0', { signerId: 'jane', capacity: 'Trustee' })SignatureCapture
A SignatureCapture records when and where a signature was placed in the document.
interface SignatureCapture {
role: string // Party role ID (e.g., "landlord", "tenant")
partyId: string // The party ID
signerId: string // The signer ID
locationId: string // Template location identifier
type: 'signature' | 'initials'
timestamp: string // ISO 8601 date-time
image?: string // Override image (if different from adopted)
method?: 'drawn' | 'typed' | 'uploaded' | 'certificate'
}Capturing Signatures
const signable = draft.prepareForSigning()
// Capture using adopted signature
const signed = signable.captureSignature('tenant', 'tenant-0', 'jane', 'page-1-sig')
// Capture with override image (one-time signature)
const signed = signable.captureSignature('tenant', 'tenant-0', 'jane', 'page-1-sig', {
image: 'data:image/png;base64,...',
method: 'drawn'
})
// Capture initials
const initialed = signed.captureInitials('tenant', 'tenant-0', 'jane', 'page-1-init')Each capture method checks its slot and throws, naming the value, when the role is not declared, the party is not filled for that role, or the signer does not sign for that party. A person party with no signatories signs for itself: pass its party id as signerId, with no addSigner. An organization with no signatories has no signer. On a sealed form, locationId must also be a signatureMap field assigned to that signer with the same type (signature, initials, capacity, or printed_name).
A slot takes one capture. Capturing the same role, party, signer, location, and type again throws. Forms are immutable, so to correct a capture, capture again from the form instance before the first capture.
Finalizing
finalize() moves a signable form to the executed phase. On a sealed form, every signatureMap field that is not required: false must have a capture first, or finalize() throws and lists the missing slots. date_signed fields are stamped when the document is signed, so they are not checked.
WitnessParty
A WitnessParty represents a witness who can attest to signatures.
interface WitnessParty {
id: string // Unique identifier
party: Person // Witness identity (always a Person)
notary?: boolean // Whether this witness is a notary public
}Adding Witnesses
const signable = draft.prepareForSigning()
// Add a regular witness
const withWitness = signable.addWitness({
id: 'witness-0',
party: { name: 'Sam Witness' }
})
// Add a notary
const withNotary = withWitness.addWitness({
id: 'notary-0',
party: { name: 'Nancy Notary' },
notary: true
})Attestation
An Attestation records a witness's attestation to one or more party signatures. It names its witness exactly once: by witnessId or inline as witness. addAttestation rejects an attestation that names no witness, or names it both ways.
type Attestation = {
signature: Signature // The witness's signature
attestsTo: AttestationTarget[] // Signatures being witnessed
} & (
| { witnessId: string } // Reference to declared witness
| { witness: WitnessParty } // Inline witness definition
)
interface AttestationTarget {
role: string // Role of the party being witnessed
partyId: string // Party ID
signerId: string // Signer ID
locationId?: string // Specific capture location (optional)
}Attestation Properties
AttestationTarget Properties
Adding Attestations
// Using a pre-declared witness
const attested = signable.addAttestation({
witnessId: 'witness-0',
signature: {
image: 'data:image/png;base64,...',
method: 'drawn',
timestamp: new Date().toISOString()
},
attestsTo: [
{ role: 'tenant', partyId: 'tenant-0', signerId: 'jane' },
{ role: 'landlord', partyId: 'landlord-0', signerId: 'bob' }
]
})
// Using inline witness definition
const attested = signable.addAttestation({
witness: { id: 'witness-1', party: { name: 'New Witness' } },
signature: { /* ... */ },
attestsTo: [{ role: 'tenant', partyId: 'tenant-0', signerId: 'jane' }]
})Signature Status
Check signature progress using status methods on SignableForm:
// Get status for a specific role
const status = signable.getSignatureStatus('tenant')
// { required: 1, collected: 1, complete: true,
// parties: [{ partyId: 'tenant-0', hasSignatory: true, hasCapture: true, witnessed: true }] }
// Get overall status
const overall = signable.getOverallSignatureStatus()
// { roles: { tenant: { required: 1, collected: 1, complete: true }, landlord: {...} },
// complete: false, totalRequired: 2, totalCollected: 1 }Related
- party - Party role definitions
- form - Form lifecycle and methods
- Signature primitive - Signature data type