DigitalOcean serverless functions for Edge. All Intercom webhooks are
handled by a single intercom/webhook function that routes by topic.
⚠️ Server-side lead→user conversion is disabled by default and should stay that way unless you understand the tradeoff below.Converting a lead to a user requires Intercom's merge API, which permanently deletes the lead profile. A customer's live Messenger session is bound to the lead's Intercom contact
id(not their email orexternal_id), so the merge orphans the session — the next message fails with "Couldn't send." Intercom confirms this is expected behaviour of merge, and there is no REST way to promote a lead to a user in place.The correct fix is client-side: when the person logs in / provides an email, your app should call
Intercom('shutdown')then boot withuser_id
user_hashif you use Identity Verification). The Messenger then transitions the lead to a user natively and keeps the session alive.
Handles three webhook topics:
contact.lead.created— lead created with an emailcontact.lead.added_email— email added to a lead that had nonecontact.email.updated— lead's email changed
How it works:
- Intercom fires a webhook to the single endpoint
- The router verifies the HMAC-SHA1 signature
- The topic is matched and dispatched to the lead-to-user handler
- If
LEAD_TO_USER_CONVERSION_ENABLEDis not truthy (the default), the handler logs and returns without touching Intercom — the lead stays a lead and keeps chatting. - Only if conversion is explicitly enabled does it search for an existing user (by the lead's id, then email), create one with email only if needed, merge the lead in, and reassign the lead's id onto the surviving user. This breaks active Messenger sessions (see warning above).
- Returns 200 so Intercom does not retry
Automatically infers a caller's timezone when an inbound call starts in
Intercom. Handles the call.started webhook topic and filters to
inbound calls only.
How it works:
- Intercom fires a
call.startedwebhook to the single endpoint - The router verifies the HMAC-SHA1 signature
- The topic is matched and dispatched to the call-timezone handler
- Parses the caller's E.164 phone number with
phonenumbers(Google's libphonenumber) to determine country and timezone - For US/CA numbers, uses the 3-digit area code to narrow to a specific timezone
- Creates an internal note on the contact with timezone details (visible in all Inbox views)
- Sets an
inferred_timezonecustom attribute on the contact (filterable, usable in reports)
edge-serverless-functions/
├── project.yml # DO Functions config
├── .env.example # Template for local dev secrets
├── README.md
└── packages/
└── intercom/
├── webhook/ # Single deployed function
│ ├── __main__.py # Router: verify sig, dispatch by topic
│ ├── intercom_client.py # Shared Intercom API client
│ ├── requirements.txt # Python dependencies
│ ├── build.sh # Dependency installer for DO
│ ├── lead_to_user/ # Lead-to-user handler
│ │ ├── __init__.py
│ │ └── handler.py
│ └── call_timezone/ # Call timezone handler
│ ├── __init__.py
│ ├── handler.py
│ └── timezone.py # Phone → timezone inference
└── tests/ # Dev/test (not deployed)
├── test_webhook.py # Lead-to-user tests
├── test_call_timezone.py # Call-timezone tests
├── test_payload.json # Sample webhook payload
└── serve_local.py # Local dev server (ngrok)
To add a new Intercom webhook handler to the router:
- Create a subpackage under
packages/intercom/webhook/:
packages/intercom/webhook/
└── your_handler/
├── __init__.py
└── handler.py # must export a handle(payload) function
- Register its topics in
packages/intercom/webhook/__main__.py:
from your_handler.handler import handle as handle_your_thing
YOUR_TOPICS = {"your.topic.name"}
# Then in main(), add a dispatch block:
if topic in YOUR_TOPICS:
return handle_your_thing(payload)-
If you need new Intercom API helpers, add them to
intercom_client.py. -
Add any new dependencies to
requirements.txt. -
Add any new environment variables in the DO Functions dashboard and update
.env.example. -
Deploy:
doctl serverless deploy . --remote-build
Deploy via the doctl CLI:
doctl auth init
doctl serverless connect
doctl serverless deploy . --remote-build
doctl serverless functions get intercom/webhook --urlSet these in the DO Functions dashboard under your namespace:
| Variable | Description |
|---|---|
INTERCOM_ACCESS_TOKEN |
Intercom API bearer token |
WEBHOOK_SECRET |
Intercom app client secret |
LEAD_TO_USER_CONVERSION_ENABLED |
Optional. Set to true to enable server-side lead→user merge. Off by default because the merge breaks live Messenger sessions (see Lead-to-User section). |
In your Intercom Developer Hub app, set the webhook endpoint URL to your function URL and subscribe to these topics:
contact.lead.createdcontact.lead.added_emailcontact.email.updatedcall.started
pip install pytest requests phonenumbers
pytest packages/intercom/tests/ -vcp .env.example .env
# Fill in real credentials
pip install requests python-dotenv phonenumbers
python3 packages/intercom/tests/serve_local.py
# In another terminal:
ngrok http 8080
# Copy the ngrok URL into Intercom webhook settings