Skip to main content

Overview

The Vouch JavaScript SDK provides email validation and device fingerprinting for browser-based applications. It’s framework-agnostic and works with any JavaScript project.
Package: @vouch/js Browser Support: All modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)

Installation

npm install @vouch/js

Quick Start

import { Vouch } from '@vouch/js';

// Initialize with your client API key
const vouch = new Vouch(
  'your-project-id',
  'your-client-api-key'
);

// Validate an email
const result = await vouch.validate('[email protected]');

if (!result.success) {
  alert('Validation failed: ' + result.error);
} else if (result.data) {
  // Check individual validations
  const { checks, deviceData, ipData } = result.data;

  if (!checks.syntax?.pass) {
    alert('Invalid email format');
  } else if (!checks.disposable?.pass) {
    alert('Disposable emails are not allowed');
  } else {
    console.log('Email is valid!');
  }
}

Initialization

Basic Initialization

import { Vouch } from '@vouch/js';

const vouch = new Vouch(projectId, apiKey);

With Options

const vouch = new Vouch(projectId, apiKey, {
  // API endpoint (default: https://api.vouch.expert)
  endpoint: 'https://api.vouch.expert',

  // API version (default: "latest")
  version: 'latest', // or 1, 2, etc.

  // Custom fingerprint configuration
  fingerprintOptions: {
    enableCanvas: true,
    enableWebGL: true,
    enableAudio: true,
    enableFonts: true
  }
});

Core Methods

validate()

Validate an email address with automatic device fingerprinting:
const result = await vouch.validate(email);
Parameters:
email
string
required
Email address to validate
The JavaScript/browser SDK automatically includes device fingerprinting. The validate method does not accept additional options - use server-side SDKs for advanced options.
Returns: Promise<ValidationResponse>
interface ValidationResponse {
  success: boolean;
  error?: string;
  statusCode?: number;
  data?: {
    checks: Record<string, CheckResult>;
    deviceData: DeviceData | null;
    ipData: IPData | null;
    signals: string[];
  };
}

interface CheckResult {
  pass: boolean;
  error?: string;
  latency: number;
  metadata?: Record<string, unknown>;
}

interface DeviceData {
  isKnownDevice: boolean;
  isNewEmail: boolean;
  emailsUsed: number;
  previousSignups: number;
  firstSeen: number;
  lastSeen?: number;
}

interface IPData {
  ip: string;
  isVPN: boolean;
  isFraud: boolean;
}

getFingerprint()

Get device fingerprint without validating an email:
const fingerprint = await vouch.getFingerprint();

console.log(fingerprint.visitorId); // Unique device ID
console.log(fingerprint.confidence); // 0-1 confidence score
Returns: Promise<Fingerprint>
interface Fingerprint {
  visitorId: string;
  confidence: number;
  signals: {
    userAgent: string;
    screenResolution: string;
    timezone: string;
    language: string;
    // ... 100+ signals
  };
}

Usage Examples

Form Validation

<form id="signup-form">
  <input type="email" id="email" required>
  <button type="submit">Sign Up</button>
  <div id="error" class="hidden"></div>
</form>

<script type="module">
  import { Vouch } from '@vouch/js';

  const vouch = new Vouch(PROJECT_ID, API_KEY);
  const form = document.getElementById('signup-form');
  const emailInput = document.getElementById('email');
  const errorDiv = document.getElementById('error');

  form.addEventListener('submit', async (e) => {
    e.preventDefault();

    const email = emailInput.value;

    try {
      const result = await vouch.validate(email);

      if (!result.success) {
        errorDiv.textContent = result.error || 'Validation failed';
        errorDiv.classList.remove('hidden');
        return;
      }

      if (result.data) {
        const { checks } = result.data;

        // Check for specific validation failures
        if (!checks.syntax?.pass || !checks.disposable?.pass) {
          errorDiv.textContent = 'This email address cannot be used';
          errorDiv.classList.remove('hidden');
          return;
        }

        // Log suspicious patterns
        if (!checks.deviceFingerprint?.pass || !checks.ipReputation?.pass) {
          console.warn('Email flagged for review:', result.data);
        }
      }

      // Proceed with signup
      submitForm(email);
    } catch (error) {
      console.error('Validation error:', error);
      errorDiv.textContent = 'Unable to validate email';
      errorDiv.classList.remove('hidden');
    }
  });
</script>

Real-time Validation

import { Vouch } from '@vouch/js';

const vouch = new Vouch(PROJECT_ID, API_KEY);
const emailInput = document.querySelector('input[type="email"]');

// Debounce validation
let timeout;
emailInput.addEventListener('input', (e) => {
  clearTimeout(timeout);

  const email = e.target.value;

  // Only validate if email looks valid
  if (!email.includes('@')) return;

  timeout = setTimeout(async () => {
    const result = await vouch.validate(email);

    // Show inline feedback
    if (!result.success || !result.data?.checks.syntax?.pass) {
      emailInput.classList.add('error');
      showError('This email cannot be used');
    } else {
      emailInput.classList.remove('error');
      hideError();
    }
  }, 500); // Wait 500ms after user stops typing
});

Check Specific Validations

const result = await vouch.validate('[email protected]');

if (result.success && result.data) {
  const { checks } = result.data;

  // Check specific validations
  if (!checks.disposable?.pass) {
    alert('Temporary emails are not allowed');
  }

  if (!checks.mx?.pass) {
    alert('Email domain has no mail servers');
  }

  if (checks.catchall?.pass === false) {
    console.warn('Domain accepts all emails');
  }
}

Error Handling

try {
  const result = await vouch.validate(email);

  if (!result.success) {
    console.error('Validation failed:', result.error);

    // Handle specific HTTP status codes
    if (result.statusCode === 400) {
      console.error('Invalid email format');
    } else if (result.statusCode === 401) {
      console.error('Invalid API key');
    } else if (result.statusCode === 402) {
      console.error('Validation quota exceeded');
    } else if (result.statusCode === 429) {
      console.error('Too many requests, please retry later');
    }
    return;
  }

  // Check validation results
  if (result.data) {
    const { checks, deviceData, ipData } = result.data;

    if (!checks.syntax?.pass) {
      throw new Error('Invalid email syntax');
    }

    if (!checks.disposable?.pass) {
      throw new Error('Disposable email not allowed');
    }
  }

} catch (error) {
  console.error('Validation error:', error.message);
}

Advanced Features

Custom Validation Logic

async function validateEmailWithCustomRules(email) {
  const result = await vouch.validate(email);

  if (!result.success || !result.data) {
    return {
      valid: false,
      reason: result.error || 'Validation failed'
    };
  }

  const { checks, deviceData, ipData } = result.data;

  // Custom business logic
  if (!checks.disposable?.pass) {
    return {
      valid: false,
      reason: 'Disposable emails not allowed'
    };
  }

  if (deviceData && deviceData.emailsUsed > 10) {
    return {
      valid: false,
      reason: 'Too many accounts from this device'
    };
  }

  if (ipData?.isVPN) {
    return {
      valid: 'flag',
      reason: 'VPN detected - requires verification'
    };
  }

  return {
    valid: true,
    reason: 'Email is valid'
  };
}

Batch Validation

async function validateMultipleEmails(emails) {
  const results = await Promise.all(
    emails.map(email => vouch.validate(email))
  );

  return results.map((result, index) => ({
    email: emails[index],
    success: result.success,
    checks: result.data?.checks,
    deviceData: result.data?.deviceData,
    error: result.error
  }));
}

// Usage
const emails = [
  '[email protected]',
  '[email protected]',
  '[email protected]'
];

const results = await validateMultipleEmails(emails);
console.log(results);

Retry Logic

async function validateWithRetry(email, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const result = await vouch.validate(email);

    if (result.success) {
      return result;
    }

    // Retry on rate limit or server errors
    if (result.statusCode === 429 || result.statusCode >= 500) {
      if (attempt < maxRetries) {
        const delay = 1000 * attempt;
        console.log(`Retrying in ${delay}ms... (attempt ${attempt}/${maxRetries})`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
    }

    // Don't retry on client errors (400, 401, etc)
    return result;
  }
}

TypeScript Support

The SDK is written in TypeScript and includes full type definitions:
import { Vouch, ValidationResponse, CheckResult, DeviceData } from '@vouch/js';

const vouch = new Vouch(projectId, apiKey);

const result: ValidationResponse = await vouch.validate('[email protected]');

if (result.success && result.data) {
  // Type-safe access to validation results
  const { checks, deviceData, ipData } = result.data;

  const disposableCheck: CheckResult | undefined = checks.disposable;

  if (disposableCheck && !disposableCheck.pass) {
    console.log('Disposable email detected');
  }

  if (deviceData && deviceData.emailsUsed > 5) {
    console.log('Multiple emails from this device');
  }
}

Browser Compatibility

FeatureChromeFirefoxSafariEdge
Email Validation90+88+14+90+
Device Fingerprinting90+88+14+90+
Canvas Fingerprinting90+88+14+90+
WebGL Fingerprinting90+88+14+90+
Audio Fingerprinting90+88+14+90+
For older browsers, use a polyfill for fetch and Promise.

CDN Usage

For projects without a build system:
<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.vouch.expert/js/v1/vouch.min.js"></script>
</head>
<body>
  <script>
    // Vouch is available globally
    const vouch = new Vouch.Vouch(PROJECT_ID, API_KEY);

    async function validateEmail(email) {
      const result = await vouch.validate(email);
      console.log(result);
    }
  </script>
</body>
</html>

Performance

  • Initialization time: < 100ms
  • Fingerprint collection: 200-500ms
  • Validation request: 150-300ms
  • Total time: 350-800ms

Common Patterns

Vue.js

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="email" type="email" required>
    <button :disabled="loading">Sign Up</button>
    <p v-if="error">{{ error }}</p>
  </form>
</template>

<script>
import { Vouch } from '@vouch/js';

const vouch = new Vouch(PROJECT_ID, API_KEY);

export default {
  data() {
    return {
      email: '',
      loading: false,
      error: null
    };
  },
  methods: {
    async handleSubmit() {
      this.loading = true;
      this.error = null;

      try {
        const result = await vouch.validate(this.email);

        if (!result.success || !result.data) {
          this.error = result.error || 'This email cannot be used';
          return;
        }

        if (!result.data.checks.syntax?.pass || !result.data.checks.disposable?.pass) {
          this.error = 'This email cannot be used';
          return;
        }

        // Proceed with signup
        await this.createAccount(this.email);
      } catch (error) {
        this.error = 'Validation failed';
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

Svelte

<script>
  import { Vouch } from '@vouch/js';

  const vouch = new Vouch(PROJECT_ID, API_KEY);

  let email = '';
  let loading = false;
  let error = null;

  async function handleSubmit() {
    loading = true;
    error = null;

    try {
      const result = await vouch.validate(email);

      if (!result.success || !result.data) {
        error = result.error || 'This email cannot be used';
        return;
      }

      if (!result.data.checks.syntax?.pass || !result.data.checks.disposable?.pass) {
        error = 'This email cannot be used';
        return;
      }

      // Proceed
      await createAccount(email);
    } catch (err) {
      error = 'Validation failed';
    } finally {
      loading = false;
    }
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <input bind:value={email} type="email" required>
  <button disabled={loading}>Sign Up</button>
  {#if error}
    <p>{error}</p>
  {/if}
</form>

Next Steps