Security
HubSpot audit before a CRM-agent retrofit: 4-block checklist
Before we quote a CRM-agent retrofit, we run a four-block audit on the HubSpot underneath. Here is what we score, and the three pipelines that usually survive.

It is 08:00 on a Tuesday in a Rotterdam logistics company. The operations lead is two coffees in. She has a HubSpot Operations Hub Professional seat, eleven sales reps, a portal that has been live since 2019, and a board paper that says AI agent on inbound by Q4. She has asked us to quote the retrofit. We have asked her for an audit window first.
Almost every CRM-agent retrofit we have shipped — fourteen agents in production at the time of writing — started with the same four-block audit. We run it before we quote. The agent is the easy part. The HubSpot underneath is usually where the project lives or dies.
This is the checklist. It is built for sub-€18M Dutch SMEs on Operations Hub Professional, where the portal is old enough to have drifted and small enough that nobody is paid full-time to keep it tidy.
Why we audit before we quote
An AI agent on top of HubSpot is, in practice, a series of reads and writes against the same custom properties, pipelines, and workflows that a human rep touches with a mouse. If those structures have drifted — and on a six-year-old portal they always have — the agent will either fail closed (refuse to update, escalate everything to a human) or fail open (write garbage into a property the reporting layer trusts).
The four blocks below score the four most common failure modes. Each block is a number out of 100. We do not quote until we know the four scores. We send the operations lead a single page; not a forty-page deck.
Block one: custom-property type-drift across the top 60 deal-stages
HubSpot lets you create a custom property once and then, over the years, lets a portal admin re-use the same internal name on a different object with a different type. The classic version: lead_bron defined as a single-line text on Contact in 2019, then re-created as an enumeration on Deal in 2022, then mirrored back to Contact as a calculated property in 2024. Three different shapes, one name, three different reporting outcomes.
We pull the top 60 deal-stages by deal volume in the last twelve months — six pipelines × the top ten stages each, roughly — and for every custom property referenced by those stages we ask three questions: what type is it on Deal, what type is it on Contact, and what type does the workflow that moves a deal into the stage actually write.
curl -s \
-H "Authorization: Bearer $HUBSPOT_PRIVATE_APP_TOKEN" \
"https://api.hubapi.com/crm/v3/properties/deals?archived=false" \
| jq '.results[] | select(.hubspotDefined != true)
| {name, type, fieldType, groupName}' \
> deals-properties.jsonWe run the same call against contacts, companies, and tickets, join on name, and flag every row where the type differs across objects. On a six-year-old portal we typically see 30 to 60 type-drifted properties. The score is the share of the top 60 deal-stages that do not touch a drifted property. Anything below 70 means we will spend the first sprint of the retrofit cleaning properties before we write a single line of agent code.
The cleanup itself is unglamorous. We export each drifted property to CSV, decide on the canonical type (almost always the one the reporting layer already reads), write a workflow that copies the old value into a newly-created property with the correct type, dry-run the migration on a sample of fifty records, and then run it on the full portal during a quiet window. Two or three drifted properties a day is a realistic pace once you factor in the dashboards and lists that reference each one and have to be re-pointed by hand. Sixty drifted properties is a three-week project, not a one-week one, and the proposal has to say so on page one.
One property worth looking at twice: hs_lead_status is a HubSpot-defined enumeration on Contact, and a portal admin who once created a custom lead_status on Deal will quietly poison every report that joins the two objects. Almost half the Dutch portals we have audited have this exact collision.
Block two: workflow-replay coverage on the top 25 enrollments
An agent enrols contacts and deals into workflows the same way a human does — by writing a property, by triggering a form, by calling the API. The question that matters is: if the agent triggers the same enrolment twice in the same minute, what happens?
HubSpot workflows are not idempotent by default. Re-enrolment is a per-workflow toggle, and even when it is on, the delay until steps inside the workflow do not always reset cleanly. We have watched a single duplicate enrolment send the same quote PDF four times to a Dutch wholesaler.
We take the top 25 workflows by enrolled-objects-per-week, replay each one against a sandbox contact, and score on three criteria: re-enrolment behaviour, delay-step convergence, and the difference between the property value the workflow reads at enrolment and the value it writes at completion. The score is the share of the 25 that survive a double-trigger without producing a side-effect a human would have to clean up.
// Replay a workflow by writing the enrolment trigger property twice
import { Client } from '@hubspot/api-client'
const hs = new Client({ accessToken: process.env.HUBSPOT_PRIVATE_APP_TOKEN })
async function replay(contactId, triggerProperty, value) {
const at = new Date().toISOString()
await hs.crm.contacts.basicApi.update(contactId, {
properties: { [triggerProperty]: value, replay_marker: at },
})
await new Promise(r => setTimeout(r, 1500))
await hs.crm.contacts.basicApi.update(contactId, {
properties: { [triggerProperty]: value, replay_marker: at + '-dup' },
})
}Anything below 80 here and we add an idempotency layer between the agent and HubSpot before we let the agent touch a workflow trigger. Usually that layer is a small queue keyed on (object_id, intent, minute_bucket), with a five-minute TTL. It is twenty lines of code and it saves the operations lead from having to write "sorry about that" emails on Monday morning.
Block three: pipelines that survive a private-app scope rotation
HubSpot private apps authenticate with a long-lived access token that carries a fixed set of scopes. The realistic operating assumption — and the security one — is that the token gets rotated, and that the rotation happens on a Friday afternoon when the person who originally wrote down the scope list is on holiday. HubSpot's own docs are explicit that the token does not auto-renew; rotation is on you.
We score this by asking a colder question: of the six-or-so pipelines in the portal, how many would still function if the agent's private-app token were re-issued tomorrow morning with only the read/write scopes for that pipeline's stages and properties, and nothing else?
The answer, on every sub-€18M portal we have audited, is three. The three are almost always:
- the primary sales pipeline,
- the renewal or upsell pipeline (if it exists as its own pipeline rather than as a stage on the primary),
- and one back-office pipeline that does not cross objects — onboarding, returns, or a billing dunning pipeline.
The pipelines that fail the rotation are the ones that have grown association requirements over time: a deal stage that silently expects a custom association to a Ticket, or a workflow that reads a Company-level property the original scope list never granted. The fix is not more scopes. The fix is to split the agent into two private apps, each with the smallest scope set it needs, and to write down which app owns which pipeline. We treat the score as the number of pipelines that survive divided by the total. Below 50 and the retrofit becomes a scope-architecture project before it becomes an agent project.
Block four: the AVG lead-bron audit trail at 08:00
The Dutch implementation of GDPR — the AVG — requires you to be able to show, for any contact in your CRM, where the lead came from and on what legal basis you are processing them. The Autoriteit Persoonsgegevens does not actually phone at 08:00, but a Dutch B2B sales floor that starts at 08:30 means the audit question lands on the operations lead's desk before the first call of the day. The trail has to be queryable in one breath.
HubSpot's hs_analytics_source is necessary and not sufficient. It tells you the bucket (organic search, direct, paid, referral, offline) but it does not tell you which form, which list import, which integration, or which human typed the contact in by hand at a trade fair. We score the portal on a single question: for a random sample of 200 contacts created in the last six months, can we reconstruct the lead-bron in fewer than three property reads?
Article 5(2) of the AVG calls this the accountability principle: a controller has to be able to demonstrate compliance, not merely claim it. A bucket property plus a written-everywhere detail property is the minimum pattern we have seen survive an internal audit at a Dutch insurer under vendor risk review. Anything looser and the answer to "where did this contact come from" turns into a forty-minute archaeology dig through workflow histories, which a sales floor at 08:35 does not have.
If yes — usually because the portal has a disciplined lead_bron_detail text property that every form, every import, and every integration writes to — the score is high and the agent can carry the trail forward by writing the same property. If no, we add the property and a small backfill before the agent goes live. The agent is the right moment to fix this; an agent that creates contacts without writing a lead-bron is a regulator's gift.
The four blocks score the portal, not the agent. If the portal scores below 70 on any one block, the retrofit quote is for cleanup first and agent second.
The single-page output
We send the operations lead one page with the four numbers and a one-line reading of each. From that page, the conversation about scope, price, and timeline writes itself. She can defend the numbers to her board. We can defend the quote to ourselves.
Block 1 Property type-drift (top 60 stages) 82 / 100
Block 2 Workflow replay (top 25 enrollments) 64 / 100
Block 3 Scope rotation survivability (pipelines) 50 / 100 (3 of 6)
Block 4 AVG lead-bron trail (sample of 200) 91 / 100Block 2 at 64 is the line item that becomes "two weeks of idempotency work" in the proposal. Block 3 at 50 is the line item that becomes "split into two private apps." Neither line item is invented to pad the quote; they fall out of the audit.
The audit takes five days
Read-only access to the portal is enough. We ask for a private app with crm.objects.deals.read, crm.objects.contacts.read, crm.objects.companies.read, automation.read, and settings.users.read, and we hand the token back at the end. We do not ask for write scopes during the audit. The deliverable is the single page above, plus a thirty-minute call to walk the four numbers.
When we built the inbound CRM agent for a Dutch industrial-supply group earlier this year, block two came back at 48 out of 100 — a portal with a beautifully drawn pipeline and twelve workflows that all double-fired on re-enrolment. We spent the first two weeks of the engagement on the idempotency queue, not on the agent. The agent then went live in week three and has not double-sent a quote since. That is the shape of the work behind every one of our AI agents: the audit first, the agent second.
The smallest thing you can do today: open your HubSpot, run the curl in block one against your own portal, and count how many of your custom Deal properties share a name with a Contact property of a different type. The number will surprise you.
Key takeaway
Before quoting a CRM-agent retrofit on HubSpot, score four blocks: property drift, workflow replay, scope rotation, and the AVG lead-bron trail.
FAQ
How long does the four-block audit take?
Three to five working days with read-only access to the portal, plus a thirty-minute call with the operations lead to walk the four scores. We hand the access token back at the end.
Which HubSpot scopes does the audit need?
Read-only is enough: crm.objects.deals.read, crm.objects.contacts.read, crm.objects.companies.read, automation.read, settings.users.read. We never ask for write scopes during audit.
What if a block scores below 50?
We still quote, but the first phase of the engagement is cleanup. The agent ships after the portal scores at least 70 on every block. The proposal makes the cleanup line items explicit.
Does this apply to Sales Hub or only Operations Hub?
The four blocks apply to any HubSpot portal with custom workflows and properties. Operations Hub Pro is where we see the most drift because of the data-sync and custom-coded action features.
Why score the AVG lead-bron trail at 08:00 specifically?
A Dutch B2B sales floor starts at 08:30. Any compliance question from a customer or partner lands before the first call of the day, so the trail has to be queryable in one breath.