ScamVerify™
Tutorials

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:

  1. Receives HubSpot contact creation events
  2. Fetches the new contact's phone number from HubSpot
  3. Calls the ScamVerify™ phone lookup API
  4. Writes the risk score, verdict, and explanation back to HubSpot custom properties
  5. 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 NameInternal NameTypeGroup
Scam Risk Scorescam_risk_scoreNumberContact Information
Scam Risk Verdictscam_risk_verdictSingle-line textContact Information
Scam Risk Detailsscam_risk_detailsMulti-line textContact 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-dotenv

Create a .env file:

SCAMVERIFY_API_KEY=sv_live_your_key_here
HUBSPOT_TOKEN=your-hubspot-private-app-token
PORT=5000

To create a HubSpot private app token, go to Settings > Integrations > Private Apps and create an app with these scopes:

  • crm.objects.contacts.read
  • crm.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

  1. Go to your HubSpot private app settings
  2. Click the Webhooks tab
  3. Set the Target URL to your server's public URL: https://your-server.com/webhooks/hubspot
  4. Click Create Subscription and select:
    • Object type: Contacts
    • Event type: Created
  5. Save the subscription

For local development, use ngrok to expose your server:

python server.py &
ngrok http 5000

Copy 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 Score is 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:

  1. The webhook fires to your server
  2. Your server logs the ScamVerify™ API call and result
  3. 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 successfully

Salesforce 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 scam verdict
  • Set up a weekly report on the percentage of high-risk leads by source

On this page