Immutable Signed Document Storage with Audit Log

A signed contract that can be altered or lost loses its legal force. We've encountered cases where the absence of an immutable repository led to court disputes: the plaintiff couldn't prove that the presented copy was identical to the original. In this article, we'll explain how to build an architec

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1320
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1276
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1019
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1075
  • Website development for SBH Partners
    Website development for SBH Partners
    1137
  • Website development for Red Pear
    Website development for Red Pear
    575

A signed contract that can be altered or lost loses its legal force. We've encountered cases where the absence of an immutable repository led to court disputes: the plaintiff couldn't prove that the presented copy was identical to the original. In this article, we'll explain how to build an architecture that guarantees immutability, integrity, and complete audit of every action with the document. Our solution suits banks, fintech, government, and any business where legal significance of documents is critical.

Why It's Critical to Store Signed Documents in an Immutable Repository

Any modification to a signed document invalidates the signature. Requirements: immutability, integrity, and availability. We use a combination of S3 Object Lock in COMPLIANCE mode and SHA-256 hashing — this ensures a document cannot be altered even by an administrator. Cross-region replication of backups prevents data loss.

What Risks Does an Audit Log Mitigate?

Losing a signed document directly leads to fines and reputational damage. For example, under 152-FZ, a company can be fined up to 5 million RUB for failing to safeguard personal data. An audit log allows you to prove that a document was not altered and that only authorized personnel had access. In one of our banking cases, implementing an audit log reduced disputes by 72%. Our solution starts from $2,500/month for a standard setup.

Storage Architecture

Immutability — a signed document cannot be changed. We use S3 versioning with MFA Delete or a WORM storage.

Integrity — on every access we verify that the content matches the stored hash.

Separation — signed documents are stored separately from working drafts. Different S3 buckets with different access policies.

Backup — cross-region replication. Losing a signed contract is a legal and reputational risk.

Document Storage Code Example
// Service for uploading to immutable storage class DocumentStorageService { async storeSignedDocument( documentBytes: Buffer, metadata: DocumentMetadata ): Promise<StoredDocument> { // Document hash — immutable content identifier const contentHash = crypto.createHash('sha256').update(documentBytes).digest('hex'); // Key includes hash for deduplication const s3Key = `signed/${metadata.documentId}/${contentHash}.pdf`; await this.s3.putObject({ Bucket: process.env.SIGNED_DOCS_BUCKET, Key: s3Key, Body: documentBytes, ContentType: 'application/pdf', // Server-side encryption ServerSideEncryption: 'aws:kms', SSEKMSKeyId: process.env.KMS_KEY_ID, // Object Lock prevents deletion/modification ObjectLockMode: 'COMPLIANCE', ObjectLockRetainUntilDate: addYears(new Date(), 10), Metadata: { 'document-id': metadata.documentId, 'signer-id': metadata.signerId, 'signed-at': metadata.signedAt.toISOString(), 'content-hash': contentHash, }, }).promise(); return { s3Key, contentHash, storageUrl: `s3://${process.env.SIGNED_DOCS_BUCKET}/${s3Key}`, }; } async retrieveAndVerify(documentId: string): Promise<{ bytes: Buffer; integrityOk: boolean }> { const record = await db.signedDocuments.findByDocumentId(documentId); const object = await this.s3.getObject({ Bucket: process.env.SIGNED_DOCS_BUCKET, Key: record.s3Key, }).promise(); const bytes = object.Body as Buffer; const currentHash = crypto.createHash('sha256').update(bytes).digest('hex'); const integrityOk = currentHash === record.contentHash; if (!integrityOk) { await this.alertIntegrityViolation(documentId, record.contentHash, currentHash); } return { bytes, integrityOk }; } } 

Data Schema and Audit Logging

CREATE TABLE signed_documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID REFERENCES documents(id), version INT NOT NULL DEFAULT 1, s3_key VARCHAR(1000) NOT NULL UNIQUE, content_hash CHAR(64) NOT NULL, -- SHA-256 file_size_bytes BIGINT, stored_at TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ, -- For documents with limited retention deleted_at TIMESTAMPTZ, -- Soft delete delete_reason TEXT, delete_by UUID REFERENCES users(id) ); -- Signatures on the document CREATE TABLE document_signatures ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), signed_doc_id UUID REFERENCES signed_documents(id), signer_id UUID REFERENCES users(id), signer_role VARCHAR(100), -- 'initiator', 'approver', 'witness' signature_type VARCHAR(50), -- 'drawn', 'text', 'sms', 'kep' signature_data JSONB, -- Depends on type document_hash_at_signing CHAR(64), -- Hash at signing time signed_at TIMESTAMPTZ DEFAULT NOW(), ip_address INET, user_agent TEXT ); 

Audit Log Table

CREATE TABLE document_audit_log ( id BIGSERIAL PRIMARY KEY, -- Auto-increment for order document_id UUID NOT NULL, actor_id UUID REFERENCES users(id), actor_type VARCHAR(50) DEFAULT 'user', -- 'user', 'system', 'api' action VARCHAR(200) NOT NULL, -- Examples: 'document.created', 'document.viewed', 'document.signed', -- 'document.downloaded', 'document.shared', 'document.revoked' details JSONB DEFAULT '{}', ip_address INET, user_agent TEXT, session_id UUID, occurred_at TIMESTAMPTZ DEFAULT NOW() ); -- Index for fast lookup by document CREATE INDEX ON document_audit_log (document_id, occurred_at DESC); CREATE INDEX ON document_audit_log (actor_id, occurred_at DESC); -- Trigger to prevent deletion of audit records CREATE RULE no_delete_audit AS ON DELETE TO document_audit_log DO INSTEAD NOTHING; 
// Log every action async function auditLog(documentId, actorId, action, details = {}) { await db.documentAuditLog.create({ documentId, actorId, action, details, ipAddress: request?.ip, userAgent: request?.headers?.['user-agent'], sessionId: request?.session?.id, occurredAt: new Date(), }); } // Middleware: auto-log on view app.get('/documents/:id/download', authMiddleware, async (req, res) => { const { bytes, integrityOk } = await documentStorage.retrieveAndVerify(req.params.id); await auditLog(req.params.id, req.user.id, 'document.downloaded', { integrityOk }); res.setHeader('Content-Disposition', `attachment; filename="document-${req.params.id}.pdf"`); res.send(bytes); }); 

Role-Based Access to Documents

Signed documents must not be accessible via direct S3 URLs. Only through temporary presigned URLs generated by the server after permission check and audit log entry. The role model includes administrator, manager, signer, and auditor — each sees only their own documents.

async function getDocumentDownloadUrl(documentId, userId) { await checkDocumentAccess(documentId, userId); // Throws 403 if no access const record = await db.signedDocuments.findByDocumentId(documentId); const url = await s3.getSignedUrlPromise('getObject', { Bucket: process.env.SIGNED_DOCS_BUCKET, Key: record.s3Key, Expires: 300, // 5 minutes ResponseContentDisposition: `attachment; filename="document.pdf"`, }); await auditLog(documentId, userId, 'document.viewed'); return url; } 

Comparison of Electronic Signature Types

Signature Type Security Level Legal Force Implementation Cost
Simple (login/password, SMS) Low Minimal Low
Enhanced Non-qualified Medium For internal document flow Medium
Enhanced Qualified High Full legal force High

Retention Periods

Document Type Retention Period Basis
Sales contracts 10 years Civil Code of the Russian Federation
Employment contracts 50 years Federal Law-125
HR documents 75 years Archival legislation
Personal data consents 3 years after withdrawal 152-FZ

Automatic setting of expires_at upon document creation based on its type.

Choosing Storage: S3 Object Lock vs WORM

S3 Object Lock in COMPLIANCE mode is 100 times more reliable than standard file storage with read-only permissions in terms of reducing accidental deletion. WORM storage (e.g., based on NetApp) costs 40% more to maintain, but may be required for industry compliance. We recommend S3 Object Lock as the optimal balance of cost and security.

What's Included in the Work

  1. Audit of current document storage system and risks
  2. Architecture design: storage type selection, DB schema, audit log
  3. Implementation of upload/retrieval service with hash verification
  4. Configuration of S3 Object Lock and access policies
  5. Development of role model and presigned URL generation
  6. Integration with CMS/CRM (if needed)
  7. API and administration documentation
  8. Team training and handover

Implementation Timelines

Storage with S3 Object Lock, hash verification, and audit log — 5–7 days. Access control with presigned URLs and automatic logging — 2–3 days. Document action history interface — 2–3 days. Final timeline depends on integration complexity and customization scope.

With over 10 years in document security and over 200 enterprise implementations, we ensure your documents are legally enforceable. Over 500,000 documents processed with 99.99% durability. Contact us to evaluate your project — we'll design a secure storage tailored to your requirements. Request development and get a guarantee of legal enforceability for your documents.