- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
How E-Signing Works in a Property Management System
About Post
From the tenant's side, signing a contract takes about as long as liking a photo. Open it, scroll, tap "Sign". Done.
From the system's side, that tap is the easy part. The hard part is being able to answer, months or years later, a few uncomfortable questions with total confidence: Who signed this? When? Which version? Were they allowed to? Has anyone changed it since?
Kate PMS, the property management system I lead the engineering on at Kate, handles more than 2,000 tenancy contracts, with contracts and e-signing as one of its core features. I won't share internals, and the examples below are simplified, but I can walk through the shape of the flow and the principles behind it. They're the same in any system that signs things.
A contract is a state machine
The first design decision, and the most important one, is to treat a contract as something that moves through clear states, with rules about who can move it and when. Roughly:
- Draft: staff prepare the contract. Terms, unit, dates, rent. Everything is editable.
- Review: someone with the right role checks it. Edits send it back to draft.
- Ready to sign: the content is frozen. This is the moment the document stops being "a form" and becomes "the thing people will agree to".
- Signed: all required parties have signed. The final document is generated and locked.
- Later states like active, renewed or terminated follow the lease itself.
Writing transitions down explicitly, instead of letting any screen update a status column, prevents a whole class of bugs. A simplified version of the idea:
enum ContractStatus: string
{
case Draft = 'draft';
case Review = 'review';
case ReadyToSign = 'ready_to_sign';
case Signed = 'signed';
public function canMoveTo(self $next): bool
{
return in_array($next, match ($this) {
self::Draft => [self::Review],
self::Review => [self::Draft, self::ReadyToSign],
self::ReadyToSign => [self::Draft, self::Signed],
self::Signed => [],
}, true);
}
}
Notice that Signed goes nowhere. A signed contract is never edited. If the terms change, that's an amendment or a new contract, with its own trail.
Freeze before you sign
The single most important rule in any signing flow: people must sign exactly what they saw, and that thing must not change afterwards.
That's why "ready to sign" exists as its own state. Once a contract reaches it, the content is locked. If someone needs to fix a typo, the contract goes back to draft, and anyone who was about to sign sees the new version, not a silently edited one.
Verifying who is signing
A signature is only meaningful if you know the signer is who they claim to be, and is entitled to sign. Users authenticate in the usual way, and role-based access controls who can prepare, review and send contracts.
For company tenants there's an extra step that I find one of the more interesting parts of the system: contract signing is verified against Commercial Registration records. The company's details are checked against its official registration before the contract can be signed. Catching a mismatch at that point is far cheaper than discovering it during a dispute.
The audit trail: write it like a diary, not a whiteboard
Every meaningful event in a contract's life gets recorded: created, edited, sent for review, approved, viewed, signed, downloaded. Each entry captures who did it, what they did, when, and relevant context about the request.
The key property is that the trail is append-only. Entries are added, never updated or deleted. A whiteboard can be wiped and rewritten; a diary with numbered pages can't, without it being obvious. When a question comes up later, the answer comes from the trail, not from someone's memory.
In Laravel terms that usually means a dedicated table written from one place in the code (often an event listener), no update or delete paths in the application, and timestamps stored in UTC so the order of events is never ambiguous.
The test I use: if a contract were disputed tomorrow, could we reconstruct its full story from the system alone, without asking anyone what they remember? If not, something is missing from the trail.
PDF integrity: proving nothing changed
When the contract is signed, the final PDF is generated. That file is the artefact people will rely on. So how do you prove later that the PDF in front of you is the one that was signed?
The standard technique is a cryptographic hash. You compute a fingerprint of the file at signing time and store it with the signing record:
$hash = hash_file('sha256', $pdfPath);
$contract->signature()->create([
'document_hash' => $hash,
'signed_at' => now(),
]);
Change a single character in the file and the SHA-256 hash changes completely. Verifying later is simple: hash the file again and compare. If they match, the document is the one that was signed. If they don't, you know immediately, and you know it's not the original.
The file itself goes into private storage, never a public URL, and is served only through an authorised download route.
What I'd tell anyone building this
- Model the lifecycle as explicit states and allowed transitions.
- Freeze the content before anyone signs, and never edit after.
- Verify identity and authority before the signature, not after.
- Keep an append-only audit trail of every meaningful action.
- Hash the final document and keep the hash with the signing record.
- Store signed files privately and serve them through access checks.
None of this is glamorous. Nobody will ever compliment the audit table. But the day someone asks "can you prove it?", it's the most valuable part of the system.
If you've built a signing or approval flow, what was the part that turned out harder than you expected?

Be first to comment it...