Build DocHub

KINDAreplaces $13.85/mosaves $166.2/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort multi-daythe files for this build are in the project pack

RuntimeNode 22 with PostgreSQLDocumentspdf.js for display, a PDF library for filling and flatteningHashingThe original and the sealed output both hashed and recordedAuditAppend-only records with no update pathDeliverySigned, expiring links per signer

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

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

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

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

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

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

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://user:pass@localhost:5432/signDocuments metadata and audit records.
DOCUMENTS_DIRrequired./data/documentsOriginals and sealed outputs, both read-only after writing.
SIGNING_LINK_TTL_DAYSrequired14How long a signing link lasts. Long enough to be reasonable, short enough to expire.
RETRIEVAL_LINK_TTL_DAYSrequired3650How long a sealed document stays retrievable. People come back years later.
CONSENT_TEXT_PATHrequired./config/consent.mdThe consent shown to signers. Recorded verbatim with each signature, so changing it is a versioned event.
EMAIL_SERVERsecretrequiredsmtp://user:pass@smtp.provider.com:587Transactional provider credentials.
RETENTION_YEARS_DEFAULTrequired7Default retention, per your written policy and its reason.

The build, in order

  1. A document that cannot change underneath anyone

    The uploaded document is hashed and preserved, and every later step references that hash.

    1. The hash is the identity from here on. The path is an implementation detail.

      Files src/documents.js

    2. Re-hash and compare. A mismatch blocks rather than warns.

      Files src/render.js

    3. pdf.js at a known scale so field coordinates are reproducible.

      Files public/viewer.js

    done when · tick each as it passes
    watch 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.
  2. Fields placed in document coordinates

    A field lands in the same place for everyone, on any screen.

    1. Document units, not screen pixels. Convert at placement, once.

      Files src/fields.js

    2. So a signer sees and can fill only their own. This is also what makes the audit attribution correct.

      Files src/fields.js

    3. Place on a desktop, open on a phone. This is the check that catches pixel coordinates.

    done when · tick each as it passes
    watch 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.
  3. Signing, and recording what was shown

    The record says what the signer saw and agreed to, not just that they signed.

    1. Expiring, unguessable, one per signer so attribution is unambiguous.

      Files src/envelopes.js

    2. Views, pages, consent text verbatim, address and client, with the document hash on each entry.

      Files src/audit.js

    3. After the consent and the fields. The order is part of the record.

      Files src/sign.js

    done when · tick each as it passes
    watch 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.
  4. Sealing, so nothing can change afterwards

    The completed document is fixed and any later alteration is detectable.

    1. No editable fields remain. Inspect the output to confirm rather than trusting the call.

      Files src/seal.js

    2. Signers, timestamps, addresses, consent and hashes. The human-readable half of the audit trail.

      Files src/certificate.js

    3. A 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 passes
    watch 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.
  5. Delivery and the copies everyone keeps

    Everyone who signed gets the sealed document and its record.

    1. Attached, so the counterparty holds a copy independent of your system.

      Files src/deliver.js

    2. Outcome and timestamp. It is part of the record that they received it.

      Files src/deliver.js

    3. Separate from signing links and long-lived. People come back years later.

      Files src/retrieve.js

    done when · tick each as it passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more documents & pdfs.