Data Quality
TypeScript
Webhook Trigger

Format Phone Numbers to E.164

Clean and standardize phone and mobilephone numbers on HubSpot contacts into international E.164 format (+1XXXXXXXXXX).

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)

standardize-phone-e164.ts
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

ParameterTypeDefaultDescription
defaultCountryCodeselect+1Country calling code applied when raw phone lacks a country prefix.

How to deploy in Workflood

1

Connect your HubSpot Portal

Add your HubSpot Private App token once. Workflood securely encrypts it via libsodium XChaCha20 and injects pre-authenticated SDK clients.

2

Create Action from Template

Select this recipe in Workflood. Edit variables, customize logic in Monaco editor, or execute dry runs with sample JSON payloads.

3

Trigger via Webhook or Cron

Trigger from standard HubSpot workflows via Webhook action, or schedule recurring batch syncs with cron expressions and timezone handling.