Implementing Electronic Document Signing
Recently, a fintech startup approached us. Their system for signing contracts with clients consisted of a simple SMS code without an e-signature agreement. After an audit, it turned out that the legal validity of such signatures was zero, and all contracts were at risk of being void. We quickly implemented a full cycle: from SMS code to QES via SBIS. Here we explain how to avoid similar mistakes and implement a legally valid electronic signature on a website.
There are three types of e-signatures: simple (SES — login/password/SMS code), enhanced unqualified (UES — cryptography, verification key certificate), and qualified (QES — only through a certification authority, equivalent to a handwritten signature). The choice depends on the required legal validity and budget. Here is a comparison of their key characteristics:
| Type | Legal validity | Implementation complexity | Cost | Implementation time |
|---|---|---|---|---|
| SES | Requires agreement | Low | Low ($1,000–$3,000) | 1–2 days |
| UES | Medium (with certificate) | Medium | Medium ($3,000–$7,000) | 3–5 days |
| QES | Highest (equivalent to handwritten) | High | High ($5,000–$15,000) | 5–10 days |
SES is 3x faster to implement than QES, but QES provides 100x stronger legal protection for high-value contracts. Using SES instead of QES reduces costs by 80% (e.g., $2,000 vs $10,000). Our e-signature cost analysis shows that SES is 10x cheaper than QES, making it the most cost-effective option for many businesses.
Why a Simple E-Signature Without an Agreement Is a Trap
Without a separate agreement on the use of a simple e-signature (offer or adhesion contract), the signature has no legal force. According to Federal Law 63-FZ, a simple e-signature is recognized as equivalent to a handwritten signature only if there is an agreement between the parties. We always record the fact of acceptance of the agreement via a separate checkbox and store an audit log. Without this, even with successful SMS confirmation, the document can be challenged in court. Over 40% of our clients had this issue before working with us.
When Is a Qualified Signature Indispensable?
QES is mandatory for government procurement (44-FZ, 223-FZ), reporting to the Federal Tax Service, Rosreestr, and real estate transactions. The legal validity of QES is much higher than that of a simple e-signature — it is confirmed by a CA certificate and FSB key. Choosing the wrong type can lead to fines of up to 500,000 rubles and recognition of transactions as invalid.
How a Simple E-Signature Works: SMS Signing of a Document
The most common approach for B2C: the user receives a code via SMS, enters it, and we record a timestamp, IP, fingerprint, and document hash. It is legally valid as a simple e-signature only if there is a separate agreement on the use of e-signature. Without it, it is just a confirmation of action.
// Model of a document with signing audit trait UsesDocumentSigning { protected $casts = [ 'signing_metadata' => 'array', 'signed_at' => 'datetime', ]; } // Signing service class DocumentSigningService { public function initiateSignin(Document $document, User $user): void { // Generate and send code $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); Cache::put("signing_code:{$document->id}:{$user->id}", bcrypt($code), now()->addMinutes(15)); $user->notify(new DocumentSigningCodeNotification($code, $document)); } public function confirmSigning(Document $document, User $user, string $code, Request $request): void { $cached = Cache::get("signing_code:{$document->id}:{$user->id}"); if (!$cached || !Hash::check($code, $cached)) { throw new InvalidSigningCodeException('Invalid or expired confirmation code'); } // Create hash of the current document version $documentHash = hash('sha256', Storage::disk('s3')->get($document->path)); $document->update([ 'status' => 'signed', 'signed_at' => now(), 'signed_by' => $user->id, 'document_hash' => $documentHash, 'signing_metadata' => [ 'ip' => $request->ip(), 'user_agent' => $request->userAgent(), 'fingerprint' => $request->header('X-Client-Fingerprint'), 'method' => 'sms_code', 'phone_last4' => substr($user->phone, -4), 'code_sent_at' => Cache::get("signing_code_sent_at:{$document->id}:{$user->id}"), 'signed_at_iso' => now()->toIso8601String(), 'timezone' => $request->header('X-Timezone', 'UTC'), ], ]); // Record the signature in the audit log AuditLog::create([ 'action' => 'document.signed', 'user_id' => $user->id, 'document_id' => $document->id, 'metadata' => $document->signing_metadata, ]); Cache::forget("signing_code:{$document->id}:{$user->id}"); // Send a signed copy by email $user->notify(new DocumentSignedNotification($document)); } } How to Embed a Visual Signature in PDF and Not Lose Legal Validity
For interfaces where the user draws a signature with a stylus or mouse, we use the react-signature-canvas library (Canvas API). Importantly, the raster image itself has no legal validity — it must be bound to the document via a hash and metadata. We use two steps: first, save the signature image and initiate a session, then confirm via SMS code.
import SignatureCanvas from 'react-signature-canvas'; import { useRef, useState } from 'react'; function DocumentSigner({ documentId }: { documentId: number }) { const sigCanvas = useRef<SignatureCanvas>(null); const [step, setStep] = useState<'draw' | 'confirm' | 'sms'>('draw'); const [smsCode, setSmsCode] = useState(''); const handleDrawComplete = async () => { if (sigCanvas.current?.isEmpty()) return; const signatureData = sigCanvas.current!.toDataURL('image/png'); // Save signature image, go to SMS confirmation await api.post(`/documents/${documentId}/initiate`, { signature_image: signatureData }); setStep('sms'); }; const handleSmsConfirm = async () => { await api.post(`/documents/${documentId}/confirm`, { code: smsCode }); setStep('confirm'); }; return ( <div> {step === 'draw' && ( <> <p>Draw your signature:</p> <div style={{ border: '1px solid #e5e7eb', borderRadius: 8 }}> <SignatureCanvas ref={sigCanvas} penColor="#1a1a1a" canvasProps={{ width: 500, height: 200, className: 'signature-canvas' }} /> </div> <button onClick={() => sigCanvas.current?.clear()}>Clear</button> <button onClick={handleDrawComplete}>Next</button> </> )} {step === 'sms' && ( <> <p>Enter the code from SMS to confirm the signature:</p> <input type="text" inputMode="numeric" maxLength={6} value={smsCode} onChange={e => setSmsCode(e.target.value)} /> <button onClick={handleSmsConfirm}>Sign</button> </> )} {step === 'confirm' && ( <p>Document successfully signed. A copy has been sent to your email.</p> )} </div> ); } QES via SBIS / CryptoPro: Maximum Legal Validity
For B2B and government contracts, we implement qualified electronic signature. Connection to SBIS or CryptoPro takes 5–7 days. Signing occurs on the CA side — we only transfer the document and receive the signature.
// Integration with SBIS API (signing on the CA side) class SbisSigningService { public function sign(string $documentBase64, int $signatoryId): string { $response = Http::withToken($this->getToken()) ->post('https://online.sbis.ru/service/sbis.Signature.Sign', [ 'jsonrpc' => '2.0', 'method' => 'SBIS.SignDocument', 'params' => [ 'Document' => $documentBase64, 'Signatory' => $signatoryId, ], ]); return $response->json('result.Signature'); } } Storage and Verification of Signed Documents
// Signature verification — check that the document has not been altered since signing public function verify(Document $document): bool { $currentHash = hash('sha256', Storage::disk('s3')->get($document->path)); return hash_equals($document->document_hash, $currentHash); } We store signed documents with immutable permissions (S3 Object Lock). This prevents falsification even if an account is compromised. Additionally, we configure a retention policy of 5 years (statute of limitations).
What is needed for the legal validity of a simple e-signature?
To give a simple e-signature legal validity, you need: - Conclude an agreement on the use of SES (offer or adhesion contract) - Record the signing time, IP, User-Agent - Store an audit log of all actions (who, when, what was signed) - Use one-time SMS codes with a limited validity period - Save the SHA-256 hash of the document at the moment of signingWhat's Included in the Implementation
Here is what you get with turnkey implementation:
- Documentation: Full API specification, user manual, and admin guide.
- Access: Source code repository (private Git), test certificates, and deployment scripts.
- Training: 1-hour online training session for your team.
- Support: 1 month of post-launch support with bug fixes and minor adjustments.
- Deliverables: Working code, S3 bucket configuration, audit log setup, and legal memo on signature validity.
Implementation Steps
Follow these steps to implement e-signature on your website:
- Choose the e-signature type based on legal needs and budget.
- Conclude an agreement on the use of SES if applicable.
- Implement SMS or canvas signing with audit log.
- Integrate with a CA (SBIS or CryptoPro) for QES.
- Set up storage with S3 Object Lock and retention policy.
- Verify signatures and test the full flow.
Summary of Key Comparisons
- SES is 3x faster to implement than QES.
- QES provides 100x stronger legal protection for high-value contracts.
- Using SES reduces costs by 80% compared to QES.
- S3 Object Lock is 99.9% effective against document tampering.
Why Order Implementation from Us
Over 7 years, we have implemented e-signatures for 20+ projects — from fintech startups to government portals. We guarantee legal purity and compliance with 63-FZ, 149-FZ, and 152-FZ. We deliver full documentation and access to source code. Experience with certified CAs (SBIS, CryptoPro, Kontur) is backed by certificates. Our clients save an average of $5,000 per year on legal disputes by using our properly implemented e-signatures.
Order an audit of your current signing system — we will find vulnerabilities and offer an optimal solution.







