Data Quality
Python
Webhook Trigger

Format Phone Numbers to E.164 (Python)

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

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 (Python)

standardize-phone-e164-python.py
Python
import re

def main(input_data, hs_client, http_client, secret, console):
    raw = input_data[0] if isinstance(input_data, list) and input_data else (input_data or {})
    contact_id = raw.get('contact_id') or raw.get('contactId') or raw.get('objectId') or raw.get('hs_object_id')
    phone = raw.get('phone')
    mobilephone = raw.get('mobilephone')
    default_country_code = input_data.get('default_country_code', '+1')

    def format_e164(val, def_code):
        if not val:
            return None
        digits = re.sub(r'[^0-9+]', '', str(val))
        if not digits:
            return None
        if digits.startswith('+'):
            return digits
        if len(digits) == 10 and def_code == '+1':
            return f"+1{digits}"
        if len(digits) == 11 and digits.startswith('1'):
            return f"+{digits}"
        return f"{def_code}{digits}" if def_code.startswith('+') else f"+{def_code}{digits}"

    # Trigger-agnostic fallback: Scan recent contacts
    if not contact_id and not phone and not mobilephone:
        contacts = hs_client.crm.contacts.basic_api.get_page(limit=25, properties=['phone', 'mobilephone'])
        count = 0
        for c in (contacts.results or []):
            p = c.properties.get('phone')
            m = c.properties.get('mobilephone')
            pFmt = format_e164(p, default_country_code) if p else None
            mFmt = format_e164(m, default_country_code) if m else None
            upd = {}
            if pFmt and pFmt != p:
                upd['phone'] = pFmt
            if mFmt and mFmt != m:
                upd['mobilephone'] = mFmt
            if upd:
                hs_client.crm.contacts.basic_api.update(c.id, properties=upd)
                count += 1
        return {"updated": count > 0, "processedCount": count}

    if contact_id and (not phone or not mobilephone):
        contact = hs_client.crm.contacts.basic_api.get_by_id(contact_id, properties=['phone', 'mobilephone'])
        phone = phone or contact.properties.get('phone')
        mobilephone = mobilephone or contact.properties.get('mobilephone')

    phone_formatted = format_e164(phone, default_country_code) if phone else None
    mobile_formatted = format_e164(mobilephone, default_country_code) if mobilephone else None

    props_to_update = {}
    if phone_formatted and phone_formatted != phone:
        props_to_update['phone'] = phone_formatted
    if mobile_formatted and mobile_formatted != mobilephone:
        props_to_update['mobilephone'] = mobile_formatted

    if contact_id and props_to_update:
        hs_client.crm.contacts.basic_api.update(contact_id, properties=props_to_update)
        return {"contactId": contact_id, "phoneFormatted": phone_formatted, "mobileFormatted": mobile_formatted, "updated": True}

    return {"contactId": contact_id, "phoneFormatted": phone_formatted, "mobileFormatted": mobile_formatted, "updated": False}

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.

Ready to run

Deploy in Workflood

Single Portal is $79/mo flat with unlimited team seats, 50,000 runs/mo, and no per-seat costs.

Node 20 & Python 3.11 runtimes
gVisor micro-container sandbox
60s execution timeout (vs HubSpot 20s)
Two-way GitHub repository sync
Instant rollback & version history