Slack alerts are great for visibility, but they don't do anything by themselves — someone still has to manually create a CRM record before outreach can start. If your team runs on HubSpot, you can skip that step entirely and have a Deal waiting in the right pipeline stage the moment a company files.
This builds on two earlier posts: pulling Form D filings with Python and turning a company name into a website for enrichment. Here we take that enriched data and push it into HubSpot as Deals via the CRM API.
Step 1: Get a HubSpot private app token
In HubSpot, go to Settings → Integrations → Private Apps, create one with crm.objects.deals.write and crm.objects.companies.write scopes, and copy the access token.
Step 2: Pull scored filings from Funding Signals
Same normalized, pre-scored data as the previous tutorials — no need to touch raw SEC EDGAR XML:
import requests
import os
FS_API_KEY = os.environ["FS_API_KEY"]
HUBSPOT_TOKEN = os.environ["HUBSPOT_TOKEN"]
resp = requests.get(
"https://fundingsignals.net/api/v1/filings",
params={"days": 1, "min_score": 70},
headers={"Authorization": f"Bearer {FS_API_KEY}"},
)
filings = resp.json()["results"]
Step 3: Upsert the Company, then create a Deal
HubSpot wants a Company object before it'll let you associate a Deal with it, so we do both in one pass:
HUBSPOT_BASE = "https://api.hubapi.com"
headers = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
def upsert_company(filing):
payload = {
"properties": {
"name": filing["company_name"],
"domain": filing.get("website", ""),
"industry": filing["industry"],
"state": filing["state"],
}
}
r = requests.post(f"{HUBSPOT_BASE}/crm/v3/objects/companies", json=payload, headers=headers)
return r.json()["id"]
def create_deal(filing, company_id):
payload = {
"properties": {
"dealname": f"{filing['company_name']} — {filing['industry']} lead",
"amount": str(filing["amount"]),
"dealstage": "appointmentscheduled",
"pipeline": "default",
"deal_source": "funding_signals",
},
"associations": [
{
"to": {"id": company_id},
"types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 5}],
}
],
}
requests.post(f"{HUBSPOT_BASE}/crm/v3/objects/deals", json=payload, headers=headers)
for filing in filings:
cid = upsert_company(filing)
create_deal(filing, cid)
deal_source is a custom property — create it under Settings → Properties → Deal properties so reps can filter "funding_signals" leads separately from inbound.
Step 4: Add a link back to the original filing
Deals in HubSpot support a description field — drop the source link in there so reps can verify the filing themselves before reaching out:
payload["properties"]["description"] = (
f"Source: https://fundingsignals.net/companies/{filing['id']}"
f"?utm_source=devto&utm_medium=tutorial6"
)
Why go straight to a Deal instead of a Contact
A Contact with no company context sits in a queue. A Deal already has an amount, an industry, and a pipeline stage attached, so it shows up on a rep's dashboard as something to work today, not something to qualify later. For funding data specifically — where timing is the whole point — skipping the qualification step is the difference between reaching out first and reaching out fifth.
Wire this up against a free Funding Signals API key if you want to see real filings flow into your own HubSpot pipeline instead of stubbing the response.
Top comments (0)