EmailVerify LogoEmailVerify

Typeform

Email checker for Typeform. Verify emails in Typeform responses and integrations.

Integrate EmailVerify with Typeform to verify email addresses as responses are submitted, ensuring high-quality leads and valid contact information.

Integration Methods

MethodBest ForComplexity
WebhooksReal-time verificationLow
ZapierNo-code automationLow
API IntegrationCustom workflowsMedium

Method 1: Webhook Integration

Set up a webhook to verify emails when form responses are submitted.

Create Webhook Endpoint

// pages/api/typeform/webhook.js
import crypto from 'crypto';
import EmailVerify from '@emailverify/sdk';

const bv = new EmailVerify(process.env.EMAILVERIFY_API_KEY);

export default async function handler(req, res) {
    if (req.method !== 'POST') {
        return res.status(405).json({ error: 'Method not allowed' });
    }

    // Verify Typeform signature
    const signature = req.headers['typeform-signature'];
    if (!verifySignature(req.body, signature)) {
        return res.status(401).json({ error: 'Invalid signature' });
    }

    const { form_response } = req.body;

    // Extract email from answers
    const email = extractEmail(form_response.answers);

    if (!email) {
        return res.status(200).json({ message: 'No email found' });
    }

    // Verify email
    const result = await bv.verify({ email });

    // Process based on result
    await processVerification(form_response, email, result);

    res.status(200).json({ success: true });
}

function verifySignature(payload, signature) {
    const secret = process.env.TYPEFORM_WEBHOOK_SECRET;
    const hash = crypto
        .createHmac('sha256', secret)
        .update(JSON.stringify(payload))
        .digest('base64');

    return `sha256=${hash}` === signature;
}

function extractEmail(answers) {
    const emailAnswer = answers.find(
        answer => answer.type === 'email'
    );

    return emailAnswer?.email || null;
}

async function processVerification(response, email, result) {
    const responseId = response.token;

    // Store verification result
    await storeResult(responseId, {
        email,
        status: result.status,
        score: result.score,
        disposable: result.is_disposable,
        verified_at: new Date().toISOString(),
    });

    // Take action based on result
    if (result.status === 'invalid' || result.is_disposable) {
        // Flag or notify
        await sendNotification({
            type: 'invalid_email',
            response_id: responseId,
            email,
            reason: result.status === 'invalid'
                ? 'Invalid email address'
                : 'Disposable email detected',
        });
    } else if (result.status === 'valid') {
        // Add to CRM, email list, etc.
        await addToCRM(email, response);
    }
}

Configure Webhook in Typeform

  1. Go to your form in Typeform
  2. Click ConnectWebhooks
  3. Click Add a webhook
  4. Enter your webhook URL: https://yoursite.com/api/typeform/webhook
  5. Copy the secret and add to your environment variables

Method 2: Zapier Integration

Connect Typeform to EmailVerify using Zapier for no-code automation.

Zap Configuration

  1. Trigger: Typeform → New Entry
  2. Action: Webhooks by Zapier → POST

Webhook Configuration

URL: https://api.emailverify.ai/v1/verify
Method: POST
Headers:
  Authorization: Bearer YOUR_API_KEY
  Content-Type: application/json
Data:
{
  "email": "{{email_field}}",
  "webhook_url": "https://hooks.zapier.com/hooks/catch/xxx/yyy"
}

Handle Results

Add a second Zap to process verification results:

  1. Trigger: Webhooks by Zapier → Catch Hook
  2. Filter: Only continue if status = "invalid" or disposable = true
  3. Action: Gmail → Send Email (notify team) or Google Sheets → Create Row (log)

Method 3: API Integration

Build a custom integration using both APIs.

Typeform Response Handler

// services/typeform-verification.ts
import EmailVerify from '@emailverify/sdk';

interface TypeformAnswer {
  type: string;
  email?: string;
  text?: string;
  field: {
    id: string;
    ref: string;
    type: string;
  };
}

interface TypeformResponse {
  token: string;
  submitted_at: string;
  answers: TypeformAnswer[];
}

interface VerificationResult {
  responseId: string;
  email: string;
  status: string;
  score: number;
  isValid: boolean;
  isDisposable: boolean;
}

class TypeformVerificationService {
  private bv: EmailVerify;

  constructor() {
    this.bv = new EmailVerify(process.env.EMAILVERIFY_API_KEY!);
  }

  async verifyResponse(response: TypeformResponse): Promise<VerificationResult | null> {
    const email = this.extractEmail(response.answers);

    if (!email) {
      return null;
    }

    const result = await this.bv.verify({ email });

    return {
      responseId: response.token,
      email,
      status: result.status,
      score: result.score,
      isValid: result.status === 'valid' && !result.is_disposable,
      isDisposable: result.is_disposable,
    };
  }

  private extractEmail(answers: TypeformAnswer[]): string | null {
    const emailAnswer = answers.find(a => a.type === 'email');
    return emailAnswer?.email || null;
  }

  async verifyBulkResponses(responses: TypeformResponse[]): Promise<VerificationResult[]> {
    const emails = responses
      .map(r => this.extractEmail(r.answers))
      .filter((email): email is string => email !== null);

    if (emails.length === 0) {
      return [];
    }

    // Use bulk verification
    const job = await this.bv.verifyBulk(emails);

    // Wait for completion
    let status;
    do {
      await this.sleep(5000);
      status = await this.bv.getBulkJobStatus(job.job_id);
    } while (status.status !== 'completed');

    const results = await this.bv.getBulkJobResults(job.job_id);

    // Map results back to responses
    return responses
      .map(response => {
        const email = this.extractEmail(response.answers);
        if (!email) return null;

        const result = results.results.find(r => r.email === email);
        if (!result) return null;

        return {
          responseId: response.token,
          email,
          status: result.status,
          score: result.score,
          isValid: result.status === 'valid' && !result.is_disposable,
          isDisposable: result.is_disposable,
        };
      })
      .filter((r): r is VerificationResult => r !== null);
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

export const typeformVerification = new TypeformVerificationService();

Fetch Typeform Responses

// services/typeform-api.ts
interface TypeformAPIResponse {
  items: TypeformResponse[];
  page_count: number;
  total_items: number;
}

class TypeformAPI {
  private apiKey: string;
  private baseUrl = 'https://api.typeform.com';

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async getResponses(formId: string, options: {
    since?: string;
    until?: string;
    page_size?: number;
  } = {}): Promise<TypeformAPIResponse> {
    const params = new URLSearchParams();

    if (options.since) params.append('since', options.since);
    if (options.until) params.append('until', options.until);
    if (options.page_size) params.append('page_size', options.page_size.toString());

    const response = await fetch(
      `${this.baseUrl}/forms/${formId}/responses?${params}`,
      {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
        },
      }
    );

    if (!response.ok) {
      throw new Error(`Typeform API error: ${response.statusText}`);
    }

    return response.json();
  }

  async getAllResponses(formId: string): Promise<TypeformResponse[]> {
    const allResponses: TypeformResponse[] = [];
    let hasMore = true;
    let before: string | undefined;

    while (hasMore) {
      const params: Record<string, string> = { page_size: '1000' };
      if (before) params.before = before;

      const queryString = new URLSearchParams(params).toString();
      const response = await fetch(
        `${this.baseUrl}/forms/${formId}/responses?${queryString}`,
        {
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
          },
        }
      );

      const data = await response.json();

      if (data.items.length === 0) {
        hasMore = false;
      } else {
        allResponses.push(...data.items);
        before = data.items[data.items.length - 1].token;
      }
    }

    return allResponses;
  }
}

export const typeformAPI = new TypeformAPI(process.env.TYPEFORM_API_KEY!);

Usage Example

import { typeformAPI } from './services/typeform-api';
import { typeformVerification } from './services/typeform-verification';

// Verify recent responses
async function verifyRecentResponses(formId: string) {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);

  const { items } = await typeformAPI.getResponses(formId, {
    since: yesterday.toISOString(),
    page_size: 100,
  });

  console.log(`Verifying ${items.length} responses...`);

  const results = await typeformVerification.verifyBulkResponses(items);

  // Generate report
  const valid = results.filter(r => r.isValid).length;
  const invalid = results.filter(r => !r.isValid).length;

  console.log(`Results: ${valid} valid, ${invalid} invalid`);

  // Flag invalid responses
  for (const result of results) {
    if (!result.isValid) {
      await flagInvalidResponse(result);
    }
  }

  return results;
}

async function flagInvalidResponse(result: VerificationResult) {
  // Add to flagged list, send notification, etc.
  console.log(`Flagging response ${result.responseId}: ${result.email}`);
}

Real-Time Form Validation

While Typeform doesn't support custom validation natively, you can use a landing page with embedded Typeform.

Pre-Validation Landing Page

<!DOCTYPE html>
<html>
<head>
    <title>Contact Form</title>
    <style>
        .form-container {
            max-width: 500px;
            margin: 50px auto;
            padding: 20px;
        }

        .pre-check {
            margin-bottom: 20px;
        }

        .pre-check input {
            width: 100%;
            padding: 12px;
            font-size: 16px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }

        .pre-check .status {
            margin-top: 8px;
            font-size: 14px;
        }

        .pre-check .status.valid { color: #28a745; }
        .pre-check .status.invalid { color: #dc3545; }
        .pre-check .status.checking { color: #666; }

        .typeform-container {
            display: none;
        }

        .typeform-container.visible {
            display: block;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <div class="pre-check">
            <label>First, let's verify your email:</label>
            <input type="email" id="email" placeholder="your@email.com" />
            <div class="status" id="status"></div>
        </div>

        <div class="typeform-container" id="typeform">
            <div data-tf-widget="FORM_ID" data-tf-hidden="email="></div>
        </div>
    </div>

    <script src="//embed.typeform.com/next/embed.js"></script>
    <script>
        const emailInput = document.getElementById('email');
        const statusEl = document.getElementById('status');
        const typeformContainer = document.getElementById('typeform');

        let verifiedEmail = null;

        emailInput.addEventListener('blur', async function() {
            const email = this.value;

            if (!email || !email.includes('@')) {
                return;
            }

            statusEl.textContent = 'Checking email...';
            statusEl.className = 'status checking';

            try {
                const response = await fetch('/api/verify-email', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ email }),
                });

                const result = await response.json();

                if (result.valid) {
                    statusEl.textContent = '✓ Email verified! Please continue below.';
                    statusEl.className = 'status valid';
                    verifiedEmail = email;

                    // Show Typeform with pre-filled email
                    showTypeform(email);
                } else {
                    statusEl.textContent = '✗ ' + (result.message || 'Please enter a valid email');
                    statusEl.className = 'status invalid';
                }
            } catch (error) {
                // Allow on error
                statusEl.textContent = '';
                showTypeform(email);
            }
        });

        function showTypeform(email) {
            typeformContainer.classList.add('visible');

            // Update hidden field
            const widget = typeformContainer.querySelector('[data-tf-widget]');
            widget.setAttribute('data-tf-hidden', `email=${encodeURIComponent(email)}`);

            // Reinitialize Typeform embed
            window.tf.createWidget();
        }
    </script>
</body>
</html>

Response Processing Workflow

Complete Workflow Handler

// services/workflow.js
import { typeformAPI } from './typeform-api';
import { typeformVerification } from './typeform-verification';

class ResponseWorkflow {
    async processNewResponse(response) {
        // Step 1: Verify email
        const verification = await typeformVerification.verifyResponse(response);

        if (!verification) {
            return { status: 'no_email' };
        }

        // Step 2: Route based on verification result
        if (!verification.isValid) {
            return await this.handleInvalidEmail(response, verification);
        }

        // Step 3: Process valid response
        return await this.handleValidEmail(response, verification);
    }

    async handleInvalidEmail(response, verification) {
        // Log invalid email
        await this.logToDatabase({
            response_id: response.token,
            email: verification.email,
            status: 'rejected',
            reason: verification.isDisposable ? 'disposable' : 'invalid',
            submitted_at: response.submitted_at,
        });

        // Notify team
        await this.sendSlackNotification({
            text: `⚠️ Invalid email submission detected`,
            blocks: [
                {
                    type: 'section',
                    text: {
                        type: 'mrkdwn',
                        text: `*Email:* ${verification.email}\n*Reason:* ${verification.isDisposable ? 'Disposable email' : 'Invalid email'}\n*Score:* ${verification.score}`,
                    },
                },
            ],
        });

        return { status: 'rejected', verification };
    }

    async handleValidEmail(response, verification) {
        // Extract all form data
        const formData = this.extractFormData(response);

        // Add to CRM
        await this.addToCRM({
            email: verification.email,
            ...formData,
            source: 'typeform',
            email_score: verification.score,
        });

        // Add to email list
        await this.addToMailchimp(verification.email, formData);

        // Log success
        await this.logToDatabase({
            response_id: response.token,
            email: verification.email,
            status: 'processed',
            submitted_at: response.submitted_at,
        });

        return { status: 'processed', verification };
    }

    extractFormData(response) {
        const data = {};

        for (const answer of response.answers) {
            const fieldRef = answer.field.ref;

            switch (answer.type) {
                case 'text':
                case 'short_text':
                case 'long_text':
                    data[fieldRef] = answer.text;
                    break;
                case 'email':
                    data[fieldRef] = answer.email;
                    break;
                case 'number':
                    data[fieldRef] = answer.number;
                    break;
                case 'choice':
                    data[fieldRef] = answer.choice?.label;
                    break;
                case 'choices':
                    data[fieldRef] = answer.choices?.labels;
                    break;
                default:
                    data[fieldRef] = answer[answer.type];
            }
        }

        return data;
    }

    async logToDatabase(data) {
        // Implement your database logging
    }

    async sendSlackNotification(message) {
        await fetch(process.env.SLACK_WEBHOOK_URL, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(message),
        });
    }

    async addToCRM(data) {
        // Implement your CRM integration
    }

    async addToMailchimp(email, data) {
        // Implement your Mailchimp integration
    }
}

export const responseWorkflow = new ResponseWorkflow();

Best Practices

1. Always Verify Webhook Signatures

function verifySignature(payload, signature) {
    const secret = process.env.TYPEFORM_WEBHOOK_SECRET;
    const hash = crypto
        .createHmac('sha256', secret)
        .update(JSON.stringify(payload))
        .digest('base64');

    return `sha256=${hash}` === signature;
}

2. Handle API Errors Gracefully

try {
    const result = await bv.verify({ email });
    // Process result
} catch (error) {
    console.error('Verification failed:', error);
    // Don't reject the submission on API errors
    await processWithoutVerification(response);
}

3. Use Hidden Fields for Pre-Verified Emails

When using a pre-validation page, pass the verified email as a hidden field:

data-tf-hidden="email={{verified_email}}"

On this page