Build DocHub
KINDAreplaces $13.85/mosaves $166.2/yrback to the verdict
Upload a document, place fields for the signer to fill, send a link, capture the signature with a full record of what was shown and agreed, produce a sealed output whose hash proves it has not changed, and keep an audit trail you could hand to someone who disputes it.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the application and the sealing pipeline.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Audit records are append-only and consequential, which wants a real database.
Get it Install locally or run it in Docker. open ↗
Verify
psql connects and reports 16 or higher - decidefree to read; an hour of advice if the documents matter
Why Electronic signature acceptance differs by jurisdiction and document type, and some documents cannot be signed this way at all. Finding out after signing is expensive.
Get it Check the position for the specific documents you intend to use this for, and record what you found and when. If it matters commercially, ask someone qualified.
Verify
A written note records the position and the date checked - API keyfree tiers cover this volume
Why Signing links and sealed documents both arrive by email, and one in a spam folder is an unsigned agreement.
Get it Create an account, add the DNS records, take the credentials.
Verify
A signing link arrives at three providers and is not in spam - have readyfree
Why Field placement, flattening and rendering all behave differently on a form generated by a word processor, a scanned document and one produced by a design tool.
Get it Take three real examples including at least one that already contains form fields, which is the case that breaks flattening.
Verify
All three upload, place fields and seal correctly
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- documents(id, name, original_key, original_hash, page_count, uploaded_at, uploader_id) - fields(id, document_id, page, x, y, w, h, kind, required, assigned_to_signer_id) · kind is text, date, checkbox or signature - envelopes(id, document_id, state, created_at, completed_at, sealed_key, sealed_hash) - signers(id, envelope_id, name, email, order, token, viewed_at, signed_at, ip_hash, user_agent) - field_values(field_id, signer_id, value, entered_at) - audit(id, envelope_id, at, actor, action, detail_json, document_hash) · append only, hash recorded with every entry - Every audit entry carries the document hash at that moment, so the record shows what was being looked at when each action happened.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://user:pass@localhost:5432/sign | Documents metadata and audit records. |
DOCUMENTS_DIR | required | ./data/documents | Originals and sealed outputs, both read-only after writing. |
SIGNING_LINK_TTL_DAYS | required | 14 | How long a signing link lasts. Long enough to be reasonable, short enough to expire. |
RETRIEVAL_LINK_TTL_DAYS | required | 3650 | How long a sealed document stays retrievable. People come back years later. |
CONSENT_TEXT_PATH | required | ./config/consent.md | The consent shown to signers. Recorded verbatim with each signature, so changing it is a versioned event. |
EMAIL_SERVERsecret | required | smtp://user:pass@smtp.provider.com:587 | Transactional provider credentials. |
RETENTION_YEARS_DEFAULT | required | 7 | Default retention, per your written policy and its reason. |
The build, in order
A document that cannot change underneath anyone
The uploaded document is hashed and preserved, and every later step references that hash.
The hash is the identity from here on. The path is an implementation detail.
Files
src/documents.jsRe-hash and compare. A mismatch blocks rather than warns.
Files
src/render.jspdf.js at a known scale so field coordinates are reproducible.
Files
public/viewer.js
done when · tick each as it passeswatch out- Storing only a path means a file replaced on disk changes what a signer agreed to with no trace. Hash on upload, record the hash with every audit entry, and verify it before rendering to a signer, so a substitution is caught rather than assumed impossible.
Fields placed in document coordinates
A field lands in the same place for everyone, on any screen.
Document units, not screen pixels. Convert at placement, once.
Files
src/fields.jsSo a signer sees and can fill only their own. This is also what makes the audit attribution correct.
Files
src/fields.jsPlace on a desktop, open on a phone. This is the check that catches pixel coordinates.
done when · tick each as it passeswatch out- Storing field positions in screen pixels at the zoom you happened to be using puts them somewhere else on every other display and in the final output. Convert to page-relative document coordinates at placement, and verify by opening the same envelope on a phone.
Signing, and recording what was shown
The record says what the signer saw and agreed to, not just that they signed.
Expiring, unguessable, one per signer so attribution is unambiguous.
Files
src/envelopes.jsViews, pages, consent text verbatim, address and client, with the document hash on each entry.
Files
src/audit.jsAfter the consent and the fields. The order is part of the record.
Files
src/sign.js
done when · tick each as it passeswatch out- A record containing only a name and a timestamp proves very little if the signature is disputed. What carries weight is the surrounding evidence: that this link was opened from this address at this time, that the document had this hash, that this consent text was displayed and accepted. Capture all of it, because none of it can be reconstructed later.
Sealing, so nothing can change afterwards
The completed document is fixed and any later alteration is detectable.
No editable fields remain. Inspect the output to confirm rather than trusting the call.
Files
src/seal.jsSigners, timestamps, addresses, consent and hashes. The human-readable half of the audit trail.
Files
src/certificate.jsA function taking a file and an envelope and reporting a match. Test with a one byte change.
Files
src/verify.js
done when · tick each as it passeswatch out- Leaving filled values as editable form fields means a recipient can change what was signed and it will look original, which defeats the whole exercise. Flatten on completion, and verify by inspecting the output for remaining form fields rather than by looking at it.
Delivery and the copies everyone keeps
Everyone who signed gets the sealed document and its record.
Attached, so the counterparty holds a copy independent of your system.
Files
src/deliver.jsOutcome and timestamp. It is part of the record that they received it.
Files
src/deliver.jsSeparate from signing links and long-lived. People come back years later.
Files
src/retrieve.js
done when · tick each as it passeswatch out- A signed document that only exists in your system is a document your counterparty does not have, and the first time they ask for it is the first time you discover the link expired. Send the file itself rather than only a link, and keep any link long-lived and separate from the signing links.
Retention, jurisdiction and honest limitsproduct builder
You keep records as long as you should, and everyone understands what this does not prove.
Per document kind, with the reason. Signed agreements often have a minimum retention rather than a maximum.
Files
docs/retention.mdsrc/retention.jsA sealed document without its audit trail loses most of its evidentiary value.
Files
scripts/backup.shOne sentence to the signer about what is verified. They are the party who most needs it.
Files
public/sign.html
done when · tick each as it passeswatch out- This verifies that someone with access to an email address completed a signing flow. It does not verify identity, and presenting it as though it does misleads both parties. Say so in the signing flow itself, because the signer is the person who most needs to know what they are agreeing to.
That is the whole plan for DocHub. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Identity verification of any kind.
- Qualified or advanced signatures under a specific legal framework.
- Advice about whether this is acceptable for a given document.
- Document storage suite integrations.
- Google integrations, hosted collaboration, signature workflow, and document delivery
- pixel-perfect proprietary PDF engine
- identity verification
- qualified trust services
- large template and integration ecosystem
- Sequential signing order, so a second signer only receives a link after the first signs
- Reusable templates with fields already placed for recurring agreements
- A timestamp from an external service, which strengthens the record at a small cost
Need the files? The project pack on the verdict page hands your agent the whole brief · more documents & pdfs.