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
| Method | Best For | Complexity |
|---|---|---|
| Webhooks | Real-time verification | Low |
| Zapier | No-code automation | Low |
| API Integration | Custom workflows | Medium |
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
- Go to your form in Typeform
- Click Connect → Webhooks
- Click Add a webhook
- Enter your webhook URL:
https://yoursite.com/api/typeform/webhook - 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
- Trigger: Typeform → New Entry
- 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:
- Trigger: Webhooks by Zapier → Catch Hook
- Filter: Only continue if status = "invalid" or disposable = true
- 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}}"