Enrich CRM Leads with Fraud Scores
Automatically score new leads in HubSpot with phone number risk data from the ScamVerify™ API.
This tutorial walks you through building a webhook handler that automatically scores every new HubSpot contact with ScamVerify™ phone risk data. When a lead fills out a form, your server calls the ScamVerify™ API, writes the risk score back to HubSpot as a custom property, and flags high-risk leads for your sales team.
Prerequisites
- Python 3.9 or later
- A ScamVerify™ API key (get one at scamverify.ai/settings/api)
- A HubSpot account with API access (private app token)
- Basic familiarity with Flask
What You Will Build
A webhook endpoint that:
- Receives HubSpot contact creation events
- Fetches the new contact's phone number from HubSpot
- Calls the ScamVerify™ phone lookup API
- Writes the risk score, verdict, and explanation back to HubSpot custom properties
- Flags high-risk leads automatically
Create custom properties in HubSpot
Before writing data back to HubSpot, you need properties to store it. Go to Settings > Properties in HubSpot and create three new contact properties:
| Property Name | Internal Name | Type | Group |
|---|---|---|---|
| Scam Risk Score | scam_risk_score | Number | Contact Information |
| Scam Risk Verdict | scam_risk_verdict | Single-line text | Contact Information |
| Scam Risk Details | scam_risk_details | Multi-line text | Contact Information |
You can also create these via the HubSpot API:
import requests
HUBSPOT_TOKEN = "your-hubspot-private-app-token"
properties = [
{
"name": "scam_risk_score",
"label": "Scam Risk Score",
"type": "number",
"fieldType": "number",
"groupName": "contactinformation",
"description": "ScamVerify phone risk score (0-100)"
},
{
"name": "scam_risk_verdict",
"label": "Scam Risk Verdict",
"type": "string",
"fieldType": "text",
"groupName": "contactinformation",
"description": "ScamVerify verdict: safe, low_risk, medium_risk, high_risk, or scam"
},
{
"name": "scam_risk_details",
"label": "Scam Risk Details",
"type": "string",
"fieldType": "textarea",
"groupName": "contactinformation",
"description": "AI-generated risk explanation from ScamVerify"
},
]
for prop in properties:
resp = requests.post(
"https://api.hubapi.com/crm/v3/properties/contacts",
headers={
"Authorization": f"Bearer {HUBSPOT_TOKEN}",
"Content-Type": "application/json",
},
json=prop,
)
print(f"{prop['name']}: {resp.status_code}")Set up the project
mkdir crm-enrichment && cd crm-enrichment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install flask requests python-dotenvCreate a .env file:
SCAMVERIFY_API_KEY=sv_live_your_key_here
HUBSPOT_TOKEN=your-hubspot-private-app-token
PORT=5000To create a HubSpot private app token, go to Settings > Integrations > Private Apps and create an app with these scopes:
crm.objects.contacts.readcrm.objects.contacts.write
Create the ScamVerify™ client
# scamverify_client.py
import requests
class ScamVerifyClient:
def __init__(self, api_key: str):
if not api_key or not api_key.startswith("sv_"):
raise ValueError("Invalid API key. Keys must start with sv_live_ or sv_test_")
self.api_key = api_key
self.base_url = "https://scamverify.ai/api/v1"
def lookup_phone(self, phone_number: str) -> dict:
response = requests.post(
f"{self.base_url}/phone/lookup",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json={"phone_number": phone_number},
timeout=30,
)
if response.status_code == 401:
raise Exception("Invalid or revoked API key")
if response.status_code == 402:
raise Exception("Quota exhausted")
if response.status_code == 429:
retry_after = response.headers.get("Retry-After", "60")
raise Exception(f"Rate limited. Retry after {retry_after}s")
response.raise_for_status()
return response.json()Create the HubSpot client
# hubspot_client.py
import requests
class HubSpotClient:
def __init__(self, token: str):
self.token = token
self.base_url = "https://api.hubapi.com"
self.headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
def get_contact(self, contact_id: str) -> dict:
"""Fetch a contact by ID with phone number property."""
response = requests.get(
f"{self.base_url}/crm/v3/objects/contacts/{contact_id}",
headers=self.headers,
params={"properties": "phone,firstname,lastname,email"},
timeout=15,
)
response.raise_for_status()
return response.json()
def update_contact(self, contact_id: str, properties: dict):
"""Update custom properties on a contact."""
response = requests.patch(
f"{self.base_url}/crm/v3/objects/contacts/{contact_id}",
headers=self.headers,
json={"properties": properties},
timeout=15,
)
response.raise_for_status()
return response.json()Build the webhook server
Create the Flask server that receives HubSpot webhook events, scores the contact's phone number, and writes the results back.
# server.py
import os
import logging
from dotenv import load_dotenv
from flask import Flask, request, jsonify
from scamverify_client import ScamVerifyClient
from hubspot_client import HubSpotClient
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
scamverify = ScamVerifyClient(os.environ["SCAMVERIFY_API_KEY"])
hubspot = HubSpotClient(os.environ["HUBSPOT_TOKEN"])
def normalize_phone(phone: str) -> str | None:
"""Extract digits and validate as US phone number."""
if not phone:
return None
digits = "".join(c for c in phone if c.isdigit())
if len(digits) == 10:
return digits
if len(digits) == 11 and digits.startswith("1"):
return digits
return None
@app.route("/webhooks/hubspot", methods=["POST"])
def handle_hubspot_webhook():
"""Process HubSpot contact creation webhook events."""
events = request.json
if not isinstance(events, list):
events = [events]
for event in events:
# HubSpot sends different event types. We only care about contact creation.
if event.get("subscriptionType") != "contact.creation":
continue
contact_id = str(event.get("objectId"))
logger.info(f"Processing new contact: {contact_id}")
try:
# Fetch the contact to get the phone number
contact = hubspot.get_contact(contact_id)
raw_phone = contact.get("properties", {}).get("phone", "")
phone = normalize_phone(raw_phone)
if not phone:
logger.info(f"Contact {contact_id} has no valid phone number, skipping")
continue
# Call ScamVerify API
result = scamverify.lookup_phone(phone)
logger.info(
f"Contact {contact_id}: score={result['risk_score']}, "
f"verdict={result['verdict']}"
)
# Write results back to HubSpot
hubspot.update_contact(contact_id, {
"scam_risk_score": str(result["risk_score"]),
"scam_risk_verdict": result["verdict"],
"scam_risk_details": result.get("explanation", ""),
})
logger.info(f"Contact {contact_id} enriched successfully")
except Exception as e:
logger.error(f"Error processing contact {contact_id}: {e}")
continue
return jsonify({"status": "ok"}), 200
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "healthy"}), 200
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port)Set up the HubSpot webhook
- Go to your HubSpot private app settings
- Click the Webhooks tab
- Set the Target URL to your server's public URL:
https://your-server.com/webhooks/hubspot - Click Create Subscription and select:
- Object type: Contacts
- Event type: Created
- Save the subscription
For local development, use ngrok to expose your server:
python server.py &
ngrok http 5000Copy the ngrok HTTPS URL and use it as the Target URL in HubSpot.
Create a HubSpot active list for high-risk contacts
In HubSpot, go to Contacts > Lists and create a new Active list with these filters:
Scam Risk Scoreis greater than 70
Name it "High-Risk Leads". This list automatically includes any contact scored above 70 by ScamVerify™.
You can use this list to:
- Exclude high-risk leads from marketing emails
- Send an internal notification to your fraud team
- Create a HubSpot workflow that assigns these contacts to a review queue
- Block high-risk contacts from booking meetings
You can also create lists for medium-risk (score 30-70) and low-risk (score under 30) contacts to build a complete lead quality pipeline.
Test the integration
Create a test contact in HubSpot with a phone number. You should see:
- The webhook fires to your server
- Your server logs the ScamVerify™ API call and result
- The contact's custom properties update with the risk score, verdict, and explanation
Check the contact record in HubSpot to verify the three custom properties are populated.
# Monitor your server logs
python server.py
# Output:
# Processing new contact: 12345
# Contact 12345: score=15, verdict=safe
# Contact 12345 enriched successfullySalesforce Adaptation
The same pattern works with Salesforce. Replace the HubSpot webhook with a Salesforce Platform Event or Outbound Message, and use the Salesforce REST API to update custom fields on the Lead or Contact object. The ScamVerify™ API calls remain identical.
Next Steps
- Add URL checking by also scanning the contact's website field
- Use the batch endpoint to score a backlog of existing contacts
- Build a HubSpot workflow that auto-disqualifies contacts with a
scamverdict - Set up a weekly report on the percentage of high-risk leads by source