Skip to content

Instantly share code, notes, and snippets.

@erikzrekz
Created July 8, 2025 16:09
Show Gist options
  • Save erikzrekz/0b47fd2804c42ca7046254d3da349208 to your computer and use it in GitHub Desktop.
Save erikzrekz/0b47fd2804c42ca7046254d3da349208 to your computer and use it in GitHub Desktop.
Hire Form Webhook Handler
async function onRequest(request, settings) {
const removeEmpty = obj => {
Object.keys(obj).forEach(
k => !obj[k] && obj[k] !== undefined && delete obj[k]
);
return obj;
};
const body = request.json().form_response;
const answers = removeEmpty(body.answers);
const hidden = removeEmpty(body.hidden);
const definition = body.definition;
const anonymousId = hidden.sid || body.token;
var traits = {};
// Create an object from the Typeform response using the field names as keys
answers.forEach(function (answer) {
const ref = answer.field.ref;
const type = answer.type;
let value;
if (type == 'text') value = answer.text;
else if (type == 'email') value = comFixer(answer.email);
else if (type == 'file_url') value = answer.file_url;
else if (type == 'url') value = answer.url;
else if (type == 'choice') value = answer.choice.label;
else if (type == 'phone_number') value = answer.phone_number;
else if (type == 'choices') value = answer.choices.labels;
else if (type == 'boolean') value = answer.boolean;
else if (type == 'number') value = answer.number;
traits[ref] = value;
});
for (trait in traits) {
var typeform;
if (trait.includes('typeform_'))
typeform = trait.replace('typeform_', '').split('_')[0];
} // determines whether something is a typeform, and which typeform it is
traits.typeform_id = body.form_id;
traits.form_fill_start_timestamp = Math.floor(
new Date(body.landed_at).getTime()
);
traits.form_fill_complete_timestamp = Math.floor(
new Date(body.submitted_at).getTime()
);
if (traits.typeform_hire_budget)
traits.typeform_hire_budget = traits.typeform_hire_budget
? traits.typeform_hire_budget.toLowerCase() == 'no'
? 'No'
: 'Yes'
: '';
if (traits.typeform_hire_remote) {
var r = traits.typeform_hire_remote;
var rl = r.toLowerCase();
if (rl == 'no') r = 'No';
else if (rl.includes('later')) r = 'Decide later';
else r = 'Yes (suggested)';
traits.typeform_hire_remote = r;
}
if (traits.sms_consent)
traits.sms_consent = traits.sms_consent == true ? true : false;
if (traits.raisinbread_consent)
traits.raisinbread_consent = traits.raisinbread_consent
.toLowerCase()
.includes('no')
? false
: true;
traits.email = traits.email || hidden.email;
traits.anonymousId = hidden.sid || body.token;
traits.test_data = hidden.test_data || '';
traits.tid = hidden.tid || '';
//fix small typeform bug where theres a random new line added sometimes
if (traits.typeform_apply_location) {
traits.typeform_apply_location = traits.typeform_apply_location.replace(
'\r',
''
);
}
if (typeform == 'hire') traits.client_type = 'Marketing';
let e = traits.email;
let remote = traits.typeform_hire_remote;
let budget = traits.typeform_hire_budget
? traits.typeform_hire_budget
: traits.typeform_hire_budget___xa;
let hire_size = traits.typeform_hire_size;
// If industry is "Local Business" or "Marketing Agency" flag as MQL
// industry is no longer an SQL disqualifier
let industry = traits.typeform_hire_industry;
// if (industry) {
// if (industry.toLowerCase() == 'local business') industry = 'no';
// }
var output = {};
output.dq = {};
output.dq_icon = {};
var email;
var domain = e.substring(e.lastIndexOf('@') + 1); // Separates the domain from the email address
let emailDqs = [
'gmail.com',
'yahoo.com',
'aol.com',
'hotmail.com',
'msn.com',
'live.com',
'me.com',
'outlook.com',
'icloud.com',
'dom.com'
]; // current array of excluded email providers. Can be replaced with a call to google sheets, but would add some time onto the function awaiting the response
emailDqs.includes(domain) ? (email = 'no') : (email = 'yes');
console.log(domain);
var qualifiers = { email, remote, budget, industry };
for (var qual in qualifiers) {
console.log(qual, qualifiers[qual]);
if (qualifiers[qual] && qualifiers[qual].toLowerCase() == 'no') {
output['dq'][qual] = true;
output['dq_icon'][qual] = '🚫';
output['dq_status'] = true;
}
}
// If coming from MAS form, mark SQL if budget and email qualify, otherwise mark MQL
if (traits.typeform_id == 'IJDlWd5d') {
if (email == 'yes' && budget.toLowerCase() == 'yes') {
output.db = 'sales qualified lead';
output.hs = 'salesqualifiedlead';
output.short = 'SQL';
} else {
output.db = 'marketing qualified lead';
output.hs = 'marketingqualifiedlead';
output.short = 'MQL';
}
output.type = 'client';
output.domain = domain;
} else if (traits.typeform_id == 'gJGxwvvf') {
output.db = 'sales qualified lead';
output.hs = 'salesqualifiedlead';
output.short = 'SQL';
output.type = 'client';
output.domain = domain;
} else if (traits.typeform_id == 'wNrg9LGC') {
output.db = 'sales qualified lead';
output.hs = 'salesqualifiedlead';
output.short = 'SQL';
output.type = 'client';
output.domain = domain;
} else if (!remote || !budget) {
output.db = 'lead';
output.hs = 'lead';
output.short = 'lead';
output.type = 'freelancer';
output.domain = domain;
} else if ((hire_size == '51-200' || hire_size == '201+') && email == 'yes') {
output.db = 'sales qualified lead';
output.hs = 'salesqualifiedlead';
output.short = 'SQL';
output.type = 'client';
output.domain = domain;
} else if (!output.dq_status) {
output.db = 'sales qualified lead';
output.hs = 'salesqualifiedlead';
output.short = 'SQL';
output.type = 'client';
output.domain = domain;
} else if (output.dq_status) {
output.db = 'marketing qualified lead';
output.hs = 'marketingqualifiedlead';
output.short = 'MQL';
output.type = 'client';
output.domain = domain;
}
Object.assign(traits, {
userId: '',
lifecycle: output.db,
// contact_type: output.type,
contact_type: output.type?.charAt(0).toUpperCase() + output.type?.slice(1),
lifecycle_dq_budget: output.dq.budget,
lifecycle_dq_email: output.dq.email,
lifecycle_dq_remote: output.dq.remote,
lifecycle_dq_budget_icon: output.dq_icon.budget,
lifecycle_dq_email_icon: typeform == 'hire' ? output.dq_icon.email : '',
lifecycle_dq_remote_icon: output.dq_icon.remote,
lifecycle_short: output.short,
email_domain: output.domain,
typeform_token: body.token,
typeform: typeform,
lifecycle_hs: output.hs,
lifecycle_short: output.short,
metadata_cid: hidden.metadata_cid || null
});
var slack_icon;
var slack_title;
switch (typeform) {
case 'apply':
slack_icon = traits.typeform_apply_hear
? 'https://app.marketerhire.com/img/' +
traits.typeform_apply_hear.toLowerCase().replace(' ', '-') +
'.png'
: ':fax:';
slack_title = traits.firstName + ' ' + traits.lastName;
break;
case 'hire':
slack_icon = traits.typeform_hire_hear
? 'https://app.marketerhire.com/img/' +
traits.typeform_hire_hear.toLowerCase().replace(' ', '-') +
'.png'
: '';
slack_title = output.short + ' - ' + traits.typeform_hire_company_name;
break;
}
Object.assign(traits, {
slack_channel: typeform
? hidden.test_data
? 'G011PGE6FFT'
: typeform === 'apply'
? 'CBLJK69J8'
: 'CBL5531HN'
: '',
slack_icon: slack_icon,
slack_title: slack_title
});
Object.assign(traits, hidden);
for (trait in traits) {
var typeform;
if (
(traits[trait] && traits[trait] == '(none)') ||
(traits[trait] && traits[trait] == '(direct)')
)
traits[trait] = '';
} // filters (none) values
Segment.identify({
anonymousId: anonymousId,
traits
});
// Build out the context object so we can use it with different events
let event_context = {
locale: hidden.locale,
ip: hidden.ip,
userAgent: hidden.client_user_agent
};
if (hidden.utm_campaign) {
event_context.campaign = {
name: hidden.utm_campaign,
source: hidden.utm_source,
medium: hidden.utm_medium,
term: hidden.utm_term,
content: hidden.utm_content
};
}
if (hidden.page_url) {
event_context.page = {
path: hidden.page_path,
referrer: hidden.page_referrer,
search: hidden.page_search,
title: hidden.page_title,
url: hidden.page_url
};
}
Segment.track({
anonymousId: anonymousId,
event: 'Typeform Submission',
properties: {
id: definition.id,
title: definition.title,
token: body.token,
action_source: 'website',
client_user_agent: hidden.client_user_agent,
event_source_url: hidden.event_source_url,
fbp: hidden.fbp,
fbc: hidden.fbc
},
context: event_context
});
Segment.track({
anonymousId: anonymousId,
event: 'Lifecycle Qualification',
properties: {
journey: 'Typeform Submission',
id: definition.id,
title: definition.title,
token: body.token,
lifecycle: traits.lifecycle
}
});
var properties = {};
Object.assign(properties, traits);
Object.assign(properties, {
userId: '',
typeform: typeform,
lifecycle_dq_budget_icon: output.dq_icon.budget,
lifecycle_dq_email_icon: output.dq_icon.email,
lifecycle_dq_remote_icon: output.dq_icon.remote,
lifecycle_hs: output.hs,
lifecycle_short: output.short,
typeform_token: body.token,
slack_channel: typeform
? hidden.test_data
? 'G011PGE6FFT'
: typeform === 'apply'
? 'CBLJK69J8'
: 'CBL5531HN'
: '',
slack_icon: traits.typeform_hire_hear
? 'https://app.marketerhire.com/img/' +
traits.typeform_hire_hear.toLowerCase().replace(' ', '-') +
'.png'
: '',
slack_title: output.short + ' - ' + properties.typeform_hire_company_name
});
Segment.track({
anonymousId: anonymousId,
event: 'Zapier Sync',
properties: properties
});
console.log(traits);
// Only send to hubspot updater if using one of the two new typeforms
if (
traits.typeform_id == 'FDxB2h' ||
traits.typeform_id == 'XJEa5Z' ||
traits.typeform_id == 'rDZZe1Vj' ||
traits.typeform_id == 'VE052uIm' ||
traits.typeform_id == 'NBWAA4uk' ||
traits.typeform_id == 'wm4ctodj' ||
traits.typeform_id == 'gJGxwvvf' ||
traits.typeform_id == 'wNrg9LGC' ||
traits.typeform_id == 'uUlXzIv2' ||
traits.typeform_id == 'ZMRagYJ6' ||
traits.typeform_id == 'GvorEoob' ||
traits.typeform_id == 'blqKnsZz' ||
traits.typeform_id == 'i35pzCoS' ||
traits.typeform_id == 'IJDlWd5d' ||
traits.typeform_id == 'ogBFs8qD' ||
traits.typeform_id == 'kRWe7d04' // new typeform Hire marketer Testing
) {
// Clone "properties" and modify it for the Hubspot Updater
try {
var updaterProperties = {};
Object.assign(updaterProperties, traits);
function replacePropKey(oldName, newName) {
Object.defineProperty(
updaterProperties,
newName,
Object.getOwnPropertyDescriptor(updaterProperties, oldName)
);
delete updaterProperties[oldName];
}
if (typeform == 'hire') {
if (traits.firstName)
replacePropKey('firstName', 'typeform_hire_firstname');
if (traits.lastName)
replacePropKey('lastName', 'typeform_hire_lastname');
if (traits.email) replacePropKey('email', 'typeform_hire_email');
if (traits.website) replacePropKey('website', 'typeform_hire_website');
if (traits.phone) replacePropKey('phone', 'typeform_hire_phone');
if (traits.lifecycle) replacePropKey('lifecycle', 'lifecyclestage');
if (traits.type) replacePropKey('type', 'traffic_type');
if (traits.slack_channel)
replacePropKey('slack_channel', 'slack_lead_in_channel');
if (traits.sid) replacePropKey('sid', 'segment_id');
if (traits[sms_consent] != undefined) {
if (traits.sms_consent)
replacePropKey('sms_consent', 'typeform_hire_sms_consent');
if (traits.raisinbread_consent)
replacePropKey(
'raisinbread_consent',
'typeform_hire_raisinbread_consent'
);
}
} else if (typeform == 'apply') {
if (traits.firstName)
replacePropKey('firstName', 'typeform_apply_firstname');
if (traits.lastName)
replacePropKey('lastName', 'typeform_apply_lastname');
if (traits.email) replacePropKey('email', 'typeform_apply_email');
if (traits.linkedin)
replacePropKey('linkedin', 'typeform_apply_linkedin');
if (traits.lifecycle) replacePropKey('lifecycle', 'lifecyclestage');
if (traits.type) replacePropKey('type', 'traffic_type');
if (traits.slack_channel)
replacePropKey('slack_channel', 'slack_lead_in_channel');
if (traits.sid) replacePropKey('sid', 'segment_id');
}
} catch (err) {
console.log(err);
}
Segment.track({
anonymousId: anonymousId,
event: 'Hubspot Sync',
properties: updaterProperties
});
}
}
function comFixer(email) {
var commonErr = [
'comm',
'ocm',
'con',
'cmo',
'copm',
'xom',
'com,',
'vom',
'comn',
"com'",
'co,',
'comj',
'coim',
'colm',
'conm',
'cok'
];
var emailArr = email.split('.');
var ending = emailArr[emailArr.length - 1];
if (commonErr.includes(ending)) emailArr[emailArr.length - 1] = 'com';
return emailArr.join('.');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment