feat: implement DocuSeal real e-signature provider (issue #23) - #28
Merged
pope-h merged 1 commit intoAug 18, 2026
Merged
Conversation
- Add DocuSeal provider with all 4 interface methods - Add hybrid persistence layer (in-memory + Postgres) - Add webhook HMAC-SHA256 signature verification - Add migration 047 for esign_requests table - Update factory to async with dynamic import for docuseal - Update lease agreements route with lazy-init provider - Add OpenAPI specs for lease agreement endpoints - Add 23 tests for DocuSeal provider (all passing)
pope-h
approved these changes
Aug 18, 2026
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the in-memory stub e-signature provider with a production-ready DocuSeal integration for lease agreement signing workflows. The stub remains the default for local development.
Closes #23
What changed
New files
src/services/docusealESignatureProvider.ts— Full DocuSeal provider implementing the existingESignatureProviderinterface (4 methods):createSigningRequest()— Creates a DocuSeal template submission via their APIgetSigningUrl()— Returns a DocuSeal-hosted signing URL for a given signerhandleWebhook()— Parses DocuSeal webhook payloads and updates request stateverifySignature()— Checks if a specific signer has completed signingverifyDocusealWebhookSignature())leaseAgreementStoresrc/services/docusealESignatureProvider.test.ts— 23 unit tests covering all provider methods, webhook signature verification, document-hash binding, and edge cases (invalid signatures, expired tokens, non-completion events)migrations/047_esign_requests.sql— Newesign_requeststable for tracking signing requests across restarts:request_id(PK),document_key,document_hash,signers(JSONB),status,provider_id,signer_states(JSONB),created_at,updated_atModified files
src/services/eSignatureService.ts— Factory function changed from sync toasync createESignatureProvider()with dynamicimport()for thedocusealcase (tree-shaking friendly)src/routes/leaseAgreements.ts— Replaced module-level eager provider instantiation with lazy-init async helpergetEsignProvider(). All 3 usage sites updated (send, sign-url, webhook routes)src/config/featureFlags.ts— UpdatedLEASE_AGREEMENTS_ENABLEDcomment to reflect real provider is now available.env.example— Added DocuSeal configuration section withESIGN_PROVIDER,DOCUSEAL_API_URL,DOCUSEAL_API_KEY,DOCUSEAL_WEBHOOK_SECRETdocs/openapi.yml— Added 8 lease endpoint paths,LeaseAgreementschema definition, andLease AgreementstagDesign decisions
No interface changes — The
ESignatureProviderinterface (4 methods) is the stable seam. DocuSeal plugs into it without modifying any existing call sites.Stub remains default —
ESIGN_PROVIDERdefaults tostub. SetESIGN_PROVIDER=docusealwith real credentials for staging/production.LEASE_AGREEMENTS_ENABLEDstaysfalseby default.Lazy provider initialization — The route now uses an async
getEsignProvider()helper instead of eagerly creating the provider at module load. This ensures the dynamic import works correctly and environment variables are read at call time.Hybrid persistence — DocuSeal requests are stored in-memory (fast reads) with Postgres fallback for durability across restarts. Follows the same hybrid pattern already established in
leaseAgreementStore.Env vars read at call time —
getDocusealApiUrl(),getDocusealApiKey(),getDocusealWebhookSecret()are getter functions, not module-level constants. This allows test stubs to inject values viaprocess.envwithout fighting module caching.Webhook signature verification — DocuSeal signs payloads with HMAC-SHA256. The provider strips the
signaturefield from the payload before verifying, matching DocuSeal's documented behavior.Document-hash binding — Each
SigningRequestcarries adocumentHashderived from the leasedocumentKeyvia SHA-256. This ties the signing request to a specific document version, preventing tampering.Environment variables
ESIGN_PROVIDERstubdocusealto enable real providerDOCUSEAL_API_URLDOCUSEAL_API_KEYDOCUSEAL_WEBHOOK_SECRETTest results