Created
July 8, 2025 16:09
-
-
Save erikzrekz/0b47fd2804c42ca7046254d3da349208 to your computer and use it in GitHub Desktop.
Hire Form Webhook Handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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