setAttribute(PDO::ATTR_TIMEOUT, 10); $pdo->exec("SELECT 0"); return $pdo; } catch(Exception $exception) { try { $error = $exception; $pdo = new PDO("sqlite:$filename"); $pdo->exec("SELECT 0"); return $pdo; } catch(Exception $exception2) { $error = $exception2; } } return $pdo; } function sqlite_escape_string($sql) { $sql = str_replace("'", "''", $sql); return $sql; } function sqlite_exec($pdo, $sql, &$error) { $rowsaffected = $pdo->exec($sql); if($rowsaffected === FALSE) { $errorinfo = $pdo->errorInfo(); $error = $errorinfo[2]; } } function sqlite_query($pdo, $sql, $fetchtype = SQLITE_ASSOC, &$error = '') { $statement = $pdo->query($sql); if($statement === FALSE) { $errorinfo = $pdo->errorInfo(); $error = $errorinfo[2]; } if($fetchtype == SQLITE_NUM) $statement->setFetchMode(PDO::FETCH_NUM); else if($fetchtype == SQLITE_ASSOC) $statement->setFetchMode(PDO::FETCH_ASSOC); else if($fetchtype == SQLITE_BOTH) $statement->setFetchMode(PDO::FETCH_BOTH); return $statement; } function sqlite_fetch_array(&$statement) { $array = $statement->fetch(); return $array; } function sqlite_fetch_single($statement) { $res = $statement->fetchColumn(); return $res; } function sqlite_close($pdo) { unset($pdo); } } //======================== DETECT =============================== if($action == 'detect') { echo 'OK'; exit(); } //======================== CREATE DB ============================ if(!file_exists($SS_DATABASE_FILENAME)) { $error = ''; $dbpointer = sqlite_open($SS_DATABASE_FILENAME, 0666, $error); if($error != '') { error_log("ERR 124: $error"); return; } $error = ''; sqlite_exec($dbpointer, 'CREATE TABLE pageview ( id INTEGER PRIMARY KEY ASC, fkbefore INTEGER, fkuser INTEGER, created DATETIME, url VARCHAR(400) ); CREATE TABLE user ( id INTEGER PRIMARY KEY ASC, uid VARCHAR(200), activity DATETIME, created DATETIME, ip VARCHAR(15), sourceurl VARCHAR(400), user_agent VARCHAR(500), http_accept VARCHAR(500), language VARCHAR(5), resolution VARCHAR(15), colors VARCHAR(20), plugins TEXT, sessiondata TEXT ); CREATE INDEX user_uid ON user(uid); CREATE INDEX user_user_agent ON user(user_agent); CREATE INDEX pageview_fkuser ON pageview(fkuser); CREATE INDEX pageview_created ON pageview(created);', $error); if($error != '') { error_log("ERR 162: $error"); return; } } else { $error = ''; $dbpointer = sqlite_open($SS_DATABASE_FILENAME, 0666, $error); if($error != '') { error_log("ERR 173: $error"); return; } } //=============================== LOG ========================================= if($action == '' || $action == 'update') { //retrieve values from _SERVER and _COOKIE $uid = isset($_COOKIE['SS_uid']) ? $_COOKIE['SS_uid'] : ''; $url = sqlite_escape_string($_SERVER['REQUEST_URI']); $ip = $_SERVER['REMOTE_ADDR']; $user_agent = sqlite_escape_string($_SERVER['HTTP_USER_AGENT']); $http_accept = sqlite_escape_string($_SERVER['HTTP_ACCEPT']); $selfhost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; $sourceurl = isset($_SERVER['HTTP_REFERER']) ? sqlite_escape_string($_SERVER['HTTP_REFERER']) : ''; $matches = array(); if(preg_match_all("/\\[[A-Za-z0-9_]*\\]/i", $SS_SESSION_FIELDS, $matches) > 0) { $sessiondata = $SS_SESSION_FIELDS; $hasdata = FALSE; foreach($matches[0] as $match) { $matchkey = trim(trim($match, '['), ']'); if(isset($SS_SERVER_DATA[$matchkey]) && ''.$SS_SERVER_DATA[$matchkey] != '') { $sessiondata = str_replace($match, $SS_SERVER_DATA[$matchkey], $sessiondata); $hasdata = TRUE; } else $sessiondata = str_replace($match, '', $sessiondata); } if(!$hasdata) $sessiondata = ''; } $language = isset($_COOKIE['SS_language']) ? sqlite_escape_string($_COOKIE['SS_language']) : ''; $resolution = isset($_COOKIE['SS_resolution']) ? sqlite_escape_string($_COOKIE['SS_resolution']) : ''; $colors = isset($_COOKIE['SS_colors']) ? sqlite_escape_string($_COOKIE['SS_colors']) : ''; $plugins = isset($_COOKIE['SS_plugins']) ? sqlite_escape_string($_COOKIE['SS_plugins']) : ''; if($url != '/favicon.ico' && $url != '/robots.txt') { if(stripos($sourceurl, $selfhost) >= 5 && stripos($sourceurl, $selfhost) <= 11) $sourceurl = ''; //merge bots if(''.$uid == '') { $botdefs = array('googlebot', 'yandexbot', 'bingbot', 'baiduspider', 'yahoo', 'mj12bot'); foreach($botdefs as $botdef) { if(stripos('__'.$user_agent, $botdef) > 1) { $res = sqlite_query($dbpointer, "SELECT uid FROM user WHERE user_agent = '$user_agent'", SQLITE_NUM, $error); $uid = ''.sqlite_fetch_single($res); unset($res); break; } } } //search for user if(''.$uid == '') { $res = sqlite_query($dbpointer, "SELECT uid FROM user WHERE user_agent = '$user_agent' AND ip = '$ip' AND http_accept = '$http_accept' AND activity > date('now', 'localtime', '-10 hour')", SQLITE_NUM, $error); if($error != '') { error_log("ERR 249: $error"); return; } $uid = ''.sqlite_fetch_single($res); unset($res); } //skip doubled pageview if(strlen($user_agent) > 2 && $url == '/' && ''.$sourceurl == '' && $http_accept == '*/*') { return; } //generate new one if(''.$uid == '') { $uid = sha1($ip.'|'.$user_agent.'|'.mt_rand(0x19A100, 0x39AA3FF)); } $sessiondata = iconv("UTF-8", "Windows-1252", $sessiondata); $sourceurl = iconv("UTF-8", "Windows-1252", $sourceurl); $url = iconv("UTF-8", "Windows-1252", $url); if($action == 'update') { $sql = "UPDATE user SET ip = '$ip', language = CASE WHEN '$language' = '' THEN language ELSE '$language' END, resolution = CASE WHEN '$resolution' = '' THEN resolution ELSE '$resolution' END, colors = CASE WHEN '$colors' = '' THEN colors ELSE '$colors' END, plugins = CASE WHEN '$plugins' = '' THEN plugins ELSE '$plugins' END WHERE uid = '$uid'"; $error = ''; sqlite_exec($dbpointer, $sql, $error); if($error != '') { error_log("ERR 286: $error"); return; } } else { //check if user already exist $error = ''; $res = sqlite_query($dbpointer, "SELECT id FROM user WHERE uid = '$uid'", SQLITE_NUM, $error); $fkuser = ''.sqlite_fetch_single($res); unset($res); if($error != '') { error_log("ERR 299: $error"); return; } if($fkuser == '') { //insert new user row. $sql = "INSERT INTO user ( uid, created, activity, ip, sourceurl, user_agent, http_accept, sessiondata, language, resolution, colors, plugins ) VALUES ( '$uid', datetime('now', 'localtime'), datetime('now', 'localtime'), '$ip', '$sourceurl', '$user_agent', '$http_accept', '$sessiondata', '$language', '$resolution', '$colors', '$plugins' );"; $error = ''; sqlite_exec($dbpointer, $sql, $error); if($error != '') { error_log("ERR 339: $error"); return; } $res = sqlite_query($dbpointer, "SELECT last_insert_rowid()"); $fkuser = ''.sqlite_fetch_single($res); unset($res); } else { $sql = "UPDATE user SET ip = '$ip', sessiondata = CASE WHEN '$sessiondata' = '' THEN sessiondata ELSE '$sessiondata' END, http_accept = '$http_accept', activity = datetime('now', 'localtime'), sourceurl = CASE WHEN sourceurl <> '' AND sourceurl IS NOT NULL THEN sourceurl WHEN ('$sourceurl' = '') THEN sourceurl ELSE '$sourceurl' END WHERE id = $fkuser"; $error = ''; sqlite_exec($dbpointer, $sql, $error); if($error != '') { error_log("ERR 367: $error"); return; } } $res = sqlite_query($dbpointer, "SELECT MAX(id) FROM pageview WHERE fkuser = $fkuser"); $fkbefore = ''.sqlite_fetch_single($res); unset($res); if($fkbefore == '') $fkbefore = 'NULL'; //insert pageview log $sql = "INSERT INTO pageview ( fkuser, fkbefore, created, url ) VALUES ( $fkuser, $fkbefore, datetime('now', 'localtime'), '$url' );"; $error = ''; sqlite_exec($dbpointer, $sql, $error); if($error != '') { error_log("ERR 394: $error"); return; } } //delete pageviews of bots older than 10 minutes $botnames = array('googlebot', 'bingbot', 'mj12bot', 'netsprint', 'baiduspider', 'yandexbot', 'ahrefsbot'); foreach($botnames as $botname) { if(stripos('_'.$user_agent, $botname) > 1) { $sql = "DELETE FROM pageview WHERE uid = '$uid' AND created < datetime('now', 'localtime', '-10 minute')"; sqlite_exec($dbpointer, $sql, $error); break; } } if($action != 'update') { ?> <", ""); $dxmlgz = tempnam(":\n\\/?><", ""); $xr = xmlwriter_open_uri($dxml); xmlwriter_start_document($xr, '1.0" encoding="Windows-1252'); xmlwriter_start_element($xr, 'root'); $islog = FALSE; //users xmlwriter_start_element($xr, 'user'); $sql = "SELECT id, created, activity, ip, sourceurl, user_agent, http_accept, sessiondata, language, resolution, colors, plugins FROM user"; if($fromactivity != '') $sql .= " WHERE activity > datetime('$fromactivity')"; $error = ''; $res = sqlite_query($dbpointer, $sql, SQLITE_ASSOC, $error); if($error != '') { error_log("ERR 546: $error"); return; } while($row = sqlite_fetch_array($res)) { xmlwriter_start_element($xr, 'row'); $colindex = 0; foreach($row as $key => $val) { $islog = TRUE; xmlwriter_write_element($xr, 'c'.$colindex, $val); $colindex++; } xmlwriter_end_element($xr); } xmlwriter_end_element($xr); unset($res); //pageviews xmlwriter_start_element($xr, 'pageview'); $sql = "SELECT id, fkuser, fkbefore, created, url FROM pageview"; if($fromid != '') $sql .= " WHERE id > $fromid "; $sql .= " ORDER BY id ASC "; $error = ''; $res = sqlite_query($dbpointer, $sql, SQLITE_ASSOC, $error); if($error != '') { error_log("ERR 577: $error"); return; } while($row = sqlite_fetch_array($res)) { xmlwriter_start_element($xr, 'row'); $colindex = 0; foreach($row as $key => $val) { $islog = TRUE; xmlwriter_write_element($xr, 'c'.$colindex, $val); $colindex++; } xmlwriter_end_element($xr); } xmlwriter_end_element($xr); unset($res); // xmlwriter_end_element($xr); xmlwriter_end_document($xr); xmlwriter_flush($xr); //compress GZIP $fs = fopen($dxml, 'rb'); $gz = gzopen($dxmlgz, 'w5'); while (!feof($fs)) { $buffer = fread($fs, 1000); gzwrite($gz, $buffer); } gzclose($gz); fclose($fs); //compress if(!$islog) { header('Content-Description: File Transfer'); header('Content-Type: none'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); } else if (file_exists($dxmlgz)) { header('Content-Description: File Transfer'); header('Content-Type: application/x-gzip'); header('Content-Disposition: attachment; filename=download.xml.gz'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); header('Content-Length: '.filesize($dxmlgz)); readfile($dxmlgz); } else if (file_exists($dxml)) { header('Content-Description: File Transfer'); header('Content-Type: text/xml'); header('Content-Disposition: attachment; filename=download.xml'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); header('Content-Length: '.filesize($dxml)); readfile($dxml); } if(file_exists($dxml)) unlink($dxml); if(file_exists($dxmlgz)) unlink($dxmlgz); } sqlite_close($dbpointer); if(isset($dbpointer)) unset($dbpointer); ?> Verdocs vs DocuSign - Verdocs

Stop Sending Your Users to DocuSign.

Embed Your Own.

Verdocs gives your developers total control — UI, workflows, pricing, and your branding. DocuSign keeps users in their ecosystem. Verdocs keeps users in yours.

Why Verdocs is the Modern Alternative to DocuSign

Built for software companies that need more than just a link to a third-party signing page.

 

Fully Embeddable & White-Label

Your product → your user flow → your brand. DocuSign branding disappears – your UI takes center stage.

Developer-First Architecture

60+ Web Components + SDKs + REST API. Launch production-grade signing in 1–2 days, not months.

Lower Cost of Ownership

Usage-based pricing beats DocuSign envelope licensing every time. Control your margins or turn signatures into revenue.

Feature Comparison

See why developers choose Verdocs over legacy providers.

 
Feature CategoryVerdocsDocuSignOthers
Fully Embeddable Signing UI Partial (iframe) Inconsistent
White-Label Emails + Certificates Limited Limited
Web Components (React/Vue) 60+ Components
Time-to-Implement 1–2 days Weeks–months Weeks
Envelope Pricing $0.40–$1.00 $1.80–$4.80 $1.50–$3.50
Revenue-sharing Opportunity
Tamper-Proof Certificates (PKI)
Seamless Customer UX Stay in product Redirects Mixed
Data Ownership & Control Full Partial Varies

Full access to sandbox & API documentation

The Business Case for Switching

Moving to Verdocs isn’t just a technical decision. It’s a financial one.

Revenue Gains

  • Charge customers for envelopes directly
  • Upgrade pricing plans by including branded signing
  • Unlock new vertical use cases inside your product

Cost Savings

  • Cut envelope costs by 50–80% immediately
  • Eliminate multiple redundant DocuSign licenses
  • Reduce churn from poor UX hand-offs

Build → Test → Ship

Faster

We abstracted the complexity of PKI, PDF generation, and field placement so you can focus on your app logic.

Web Components

Drop-in UI that inherits your CSS

Rest API

Predictable endpoints, great docs

				
					import { VerdocsSign } from '@verdocs/web-sdk';

// Initialize the component
const signSession = new VerdocsSign({
  envelopeId: 'env_123456789',
  roleId: 'signer_1',
  branding: {
    primaryColor: '#3b82f6',
    logoUrl: 'https://your-app.com/logo.png'
  }
});

// Mount to your DOM
signSession.mount('#signing-container');

// Listen for completion
signSession.on('finish', (event) => {
  console.log('Signed!', event.pdfUrl);
  router.push('/dashboard/success');
});
				
			
				
					// Express.js route handler
app.post('/webhooks/verdocs', async (req, res) => {
  const signature = req.headers['x-verdocs-signature'];
  const event = req.body;

  // 1. Verify webhook authenticity
  if (!isValidSignature(event, signature)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Handle specific event types
  switch (event.type) {
    case 'envelope.completed':
      await db.contracts.update({ 
        status: 'active', 
        signed_at: new Date() 
      });
      await notifyUser(event.signer_email);
      break;
      
    case 'signer.viewed':
      analytics.track('Contract Viewed');
      break;
  }

  res.status(200).send('OK');
});
				
			
				
					const response = await fetch('https://api.verdocs.com/envelopes', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    template_id: 'tmpl_987654321',
    roles: [
      {
        name: 'Customer',
        email: 'customer@example.com',
        fields: {
          'company_name': 'Acme Corp',
          'contract_value': '$10,000'
        }
      }
    ],
    metadata: {
      crm_deal_id: 'deal_555'
    }
  })
});

const { envelope_link } = await response.json();
				
			

Pricing that Scales with You

Transparent usage-based pricing. No seat licenses. No hidden minimums.

Verdocs

$ 0.40
/ envelope
  • Transparent, pay-as-you-go pricing for envelopes. Volume discounts available.

DocuSign

$ 4.80+
/ envelope
  • Estimated cost based on seat licenses + standard envelope allotments.

Automatic margin win for platforms processing 10K+ envelopes/year.

Teams Who Switched Say It’s a No-Brainer

“We implemented Verdocs in 2 days and now own 100% of our signing UX. It completely removed the friction we saw with DocuSign redirects.”

Alex V.

CTO, FinTech StartUp

Frequently Asked Questions

Yes. Verdocs adheres to UETA and ESIGN acts, making signatures legally binding in the United States. We also provide comprehensive audit trails for every transaction.

No. Your users sign directly within your application interface. They never have to create a Verdocs account, ensuring a frictionless experience.

Yes! Our team provides migration tools and white-glove support to help you port existing PDF templates and field mappings into Verdocs.

Most developers get their first envelope sent via API within 30 minutes. A full embedded POC can usually be completed in 1-2 days.

Why choose Verdocs instead of DocuSign?

Verdocs provides a full white-label experience that puts you in control. Unlike DocuSign, we offer developer-owned workflows, data sovereignty, and a significantly lower Total Cost of Ownership. Ideal for SaaS, fintech, insurance, and compliance-driven sectors looking to monetize or integrate signatures deeply.

Ready to embed your signature experience?

Get your API key and start sending in minutes.