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.







