Why this automation is needed
Native HubSpot workflows often struggle with complex multi-object traversals, conditional lookback attribution windows, custom phone format standardizations, and custom scoring calculations.
With Workflood, this recipe runs in a secure gVisor container sandbox with pre-authenticated HubSpot SDK injection, avoiding HubSpot Data Hub execution limits, memory bottlenecks, and per-seat fees.
Action Source Code (TypeScript)
export default async function main(input: {
contactId?: string
phone?: string
mobilephone?: string
defaultCountryCode?: string
[key: string]: unknown
}): Promise<{
contactId?: string
phoneFormatted?: string
mobileFormatted?: string
updated: boolean
processedCount?: number
}> {
const rawInput = (Array.isArray(input) ? input[0] : input) || {}
let contactId = (rawInput.contactId || rawInput.contact_id || rawInput.objectId || (rawInput.object && (rawInput.object as Record<string, unknown>).objectId) || rawInput.hs_object_id) as string | undefined
let phone = (rawInput.phone || (rawInput.fields && (rawInput.fields as Record<string, unknown>).phone)) as string | undefined
let mobilephone = (rawInput.mobilephone || (rawInput.fields && (rawInput.fields as Record<string, unknown>).mobilephone)) as string | undefined
const defaultCountryCode = (rawInput.defaultCountryCode || input.defaultCountryCode) as string | undefined
const countryCode = defaultCountryCode || '+1'
function formatE164(raw: string, defCode: string): string | null {
if (!raw) return null
let digits = raw.replace(/[^0-9+]/g, '')
if (!digits) return null
if (digits.startsWith('+')) return digits
if (digits.length === 10 && defCode === '+1') return `+1${digits}`
if (digits.length === 11 && digits.startsWith('1')) return `+${digits}`
return defCode.startsWith('+') ? `${defCode}${digits}` : `+${defCode}${digits}`
}
// Trigger-Agnostic Fallback: Batch format recent contacts
if (!contactId && !phone && !mobilephone) {
const contacts = await hubspot.client.crm.contacts.basicApi.getPage(
25, 0, ['phone', 'mobilephone', 'firstname', 'lastname']
)
let count = 0
for (const c of contacts.results) {
const p = c.properties.phone
const m = c.properties.mobilephone
const pFmt = p ? formatE164(p, countryCode) : null
const mFmt = m ? formatE164(m, countryCode) : null
const upd: Record<string, string> = {}
if (pFmt && pFmt !== p) upd.phone = pFmt
if (mFmt && mFmt !== m) upd.mobilephone = mFmt
if (Object.keys(upd).length > 0) {
await hubspot.client.crm.contacts.basicApi.update(c.id, { properties: upd })
count++
}
}
return { updated: count > 0, processedCount: count }
}
if (contactId && (!phone || !mobilephone)) {
const contact = await hubspot.client.crm.contacts.basicApi.getById(
contactId,
['phone', 'mobilephone']
)
phone = phone || contact.properties.phone
mobilephone = mobilephone || contact.properties.mobilephone
}
const phoneFormatted = phone ? (formatE164(phone, countryCode) || undefined) : undefined
const mobileFormatted = mobilephone ? (formatE164(mobilephone, countryCode) || undefined) : undefined
const propertiesToUpdate: Record<string, string> = {}
if (phoneFormatted && phoneFormatted !== phone) {
propertiesToUpdate.phone = phoneFormatted
}
if (mobileFormatted && mobileFormatted !== mobilephone) {
propertiesToUpdate.mobilephone = mobileFormatted
}
if (contactId && Object.keys(propertiesToUpdate).length > 0) {
await hubspot.client.crm.contacts.basicApi.update(contactId, {
properties: propertiesToUpdate,
})
return { contactId, phoneFormatted, mobileFormatted, updated: true, processedCount: 1 }
}
return { contactId, phoneFormatted, mobileFormatted, updated: false, processedCount: 0 }
}
Configurable Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| defaultCountryCode | select | +1 | Country calling code applied when raw phone lacks a country prefix. |
How to deploy in Workflood
Connect your HubSpot Portal
Add your HubSpot Private App token once. Workflood securely encrypts it via libsodium XChaCha20 and injects pre-authenticated SDK clients.
Create Action from Template
Select this recipe in Workflood. Edit variables, customize logic in Monaco editor, or execute dry runs with sample JSON payloads.
Trigger via Webhook or Cron
Trigger from standard HubSpot workflows via Webhook action, or schedule recurring batch syncs with cron expressions and timezone handling.
Deploy in Workflood
Single Portal is $79/mo flat with unlimited team seats, 50,000 runs/mo, and no per-seat costs.
