GoHighLevel to n8n Bridge
Module 14 · business · intermediate
normalize.js
// Accept both GoHighLevel outbound shapes and return one clean lead:
// - "Custom Webhook" action: the body you define (recommended, see lesson Step 1)
// - "Webhook" action: standard contact fields, your extra fields inside customData
const req = $input.first().json;
const b = req.body ?? {};
const cd = b.customData ?? {};
const pick = (...keys) => {
for (const k of keys) {
const v = cd[k] ?? b[k] ?? b.contact?.[k];
if (v !== undefined && v !== null && String(v).trim() !== '') return String(v).trim();
}
return '';
};
const first = pick('first_name', 'firstName');
const last = pick('last_name', 'lastName');
const tags = [].concat(b.tags ?? cd.tags ?? []).flatMap((t) => String(t).split(',')).map((t) => t.trim()).filter(Boolean);
return [{
json: {
contact_id: pick('contact_id', 'contactId', 'id'),
location_id: pick('location_id', 'locationId') || b.location?.id || '',
name: pick('full_name', 'fullName', 'name') || [first, last].filter(Boolean).join(' '),
first_name: first,
email: pick('email').toLowerCase(),
phone: pick('phone').replace(/[^\d+]/g, ''),
source: pick('source', 'contact_source', 'lead_source').toLowerCase(),
message: pick('message', 'notes', 'details', 'how_can_we_help').slice(0, 3000),
company: pick('company_name', 'companyName', 'company'),
website: pick('website'),
tags,
dry_run: req.query?.dry_run === 'true',
},
}];