[AVAILABLE], hermetic test suite plus a live run against a real HubSpot
developer/test account.
packages/connector-hubspot/. HubSpot, Paytm, GitHub,
and Slack are the real, wired connectors on the default server; everything
else offered as a reference (SAP, Salesforce, Workday, Oracle) is a mock, and
no other capability registers by default at all, see The
gateway.What this connector does
Updates one property, or two, on one HubSpot object type: a Deal’sdealstage, optionally
alongside amount, in a single PATCH /crm/v3/objects/deals/{dealId} call. Nothing else.
It does not touch Contacts or Companies, does not delete or archive deals, has no webhook or
event-driven trigger, and does not perform multi-object writes, see [ROADMAP] below. It does
not change anything about the Execution Gateway, how policies are evaluated (see Policies
and the decision), Replay, Receipt Generation,
Verification, or the REST API, those work exactly as documented elsewhere on this site.
@parmana/connector-hubspot is a standalone package, not a subdirectory of
@parmana/connector-sdk like the four reference mocks and Razorpay, it depends on
@parmana/connector-sdk’s Connector contract the same way they do.
Deny-by-default at the property level
Ahubspot:deal-update request naming any property other than dealstage or amount is
refused before any network call, not silently dropped:
The policy model
Every deal update and every deal read needs a signed human approval (since 2026-09-30). No AI agent action is authorized without one (Human approval).hubspot:deal-updateis bound topolicies/hubspot-deal-update/1.1.0/policy.json. It authorizes an update only withdealUpdateApproved: trueand a signed approval for that deal (parameters.dealId) insignals.approvalArtifact, whatever the amount. It still rejects a proposeddealstagetransition unless it moves strictly forward through a fixed default stage order (appointmentscheduled→qualifiedtobuy→presentationscheduled→decisionmakerboughtin→contractsent→closedwon), with one explicit exception: moving toclosedlostis allowed from any non-terminal stage. Any move backward, any move out of a terminal stage (closedwonorclosedlost), and any move to or from a stage id the order doesn’t recognize, are all rejected, even with an approval.hubspot:deal-fetchis bound to its own policy,policies/hubspot-deal-read/1.0.0/policy.json, which authorizes a read only withreadApproved: trueand a signed approval for that deal.
HubSpotSignalStateVerifier (packages/connector-hubspot/src/HubSpotSignalStateVerifier.ts)
still fetches the real deal and refuses an update whose declared stage and amount facts do not
match it. The approval itself is checked by ApprovalSignalVerifier, like every other action:
trusted and unrevoked approver, signature, expiry, action, deal, single use.
Before 1.1.0.
hubspot-deal-update 1.0.0 authorized an update within the
amount threshold (10,000) with no person, and an amount change above it only
with preAuthorizedForAmountChange: true backed by a signed approval, which
HubSpotSignalStateVerifier checked itself. It also governed reads. 1.0.0 now
fails to load. Under 1.1.0 the verifier skips that pre authorization check,
because the policy declares approvalSignals and the one approval covers the
whole update. The approval covers the deal, not the new stage or amount: the
approver trusts the agent with those for that one use.boundSignals is present from this policy’s first version, not added after a live
demonstration of a signal/intent mismatch the way razorpay-refund/1.0.0 was, on the
now-removed Razorpay connector (see Policies and the
decision for that history):
proposedDealStage/proposedAmount signals must exactly equal
intent.parameters.dealstage/intent.parameters.amount, checked by SignalIntentBinder
before policy evaluation ever runs, so the facts the policy approved describe the same
update that actually executes, not just a payload that happens to look correct in
isolation.
Try it: an authorized update and a denied one
An authorized request,dealstage moving forward one step, and a denied one, an attempted
backward move, sent to the real, production-wired POST /execute route in a hermetic test
process (packages/api/tests/integration/hubspot-deal-update.integration.test.ts), captured
from an actual local run, 2026-07-31:
qualifiedtobuy back to
appointmentscheduled), rejected before the connector is ever dispatched, zero HubSpot API
calls, confirmed by a fetch spy in the underlying test, not just by inspecting the
response:
Try it without touching HubSpot at all
MockHubSpotServer with a placeholder test-mode token and drives one deal update through
the exact same production bootstrap chain server.ts calls (createExecutionSystem →
createApplication), pointed at the mock server via the connector’s test seam, loading the
real policy above from disk. Real output from a run against this exact code:
dealstage/amount update only,
nothing else demonstrated here.
Setup
Create a Private App in the target HubSpot account (Settings → Integrations → Private Apps), scoped to exactly two CRM scopes:crm.objects.deals.readcrm.objects.deals.write
Configuration
What’s proven where, precisely
This is the part worth reading in full before deciding how much to trust this connector.- 43 unit tests across two packages hit
MockHubSpotServer, not the real network, and run on everynpm test, no gate required:packages/connector-hubspot/tests/unit/(31 tests, includingHubSpotSignalStateVerifier.test.ts’s Approval Artifact verification suite — a matching artifact, an unknown issuer, a scope-escalated amount, a deal-id mismatch, and a replayed nonce, each independently rejected or approved) andpackages/execution-gateway/tests/unit/hubspot-connector.test.ts(12 tests). Together these cover the full authorize → verify → execute → confirm chain, every policy rule branch, replay with zero second calls,businessTransactionHashtamper rejection, and the placeholder-credential guard against a real-shaped endpoint. packages/api/tests/integration/hubspot-deal-update.integration.test.ts(6 tests) drives the same real, production-wiredPOST /executeroute this page’s examples above are captured from, againstMockHubSpotServer. A policy denial here is proven to make zero HubSpot calls two ways: the mock server’s own state is confirmed unchanged, and afetchspy confirms literally nothing reached its base URL.packages/api/tests/integration/hubspot-live.integration.test.ts(3 tests) is the only test in this codebase that calls the real HubSpot API for this connector. Gated behindALLOW_LIVE_HUBSPOT=1plus a realTEST_HUBSPOT_PRIVATE_APP_TOKEN(must start withpat-, checked before any network call), does not run in a defaultnpm test. Run to completion against a real HubSpot developer/test account: a reachability check against a non-existent deal id, a policy denial proven to make zero real HubSpot calls, and a non-destructive amount change against a real test deal, read live, nudged by a small within-threshold delta, verified via an independent live read, then reverted to its original value in the same run, safe to repeat, unlike an irreversible action.
[ROADMAP]
- Contacts and Companies. No implementation exists. This connector covers Deal
dealstage/amountupdate only. - Delete or archive a deal. No implementation exists, deny-by-default this milestone
touches only
dealstage/amounton an existing deal. - A webhook or event-driven trigger. No implementation exists. This connector is request-response only; the now-removed Razorpay connector had an asynchronous confirmation loop for its refund lifecycle, but that mechanism was removed along with it 2026-08-12.
- Multi-object transactions. No implementation exists, each
hubspot:deal-updatecall is a single Deal write. - Per-pipeline stage-transition rules. The forward-only stage order above is one global
order, not keyed by a deal’s own
pipelineproperty.isHubSpotStageTransitionAllowedaccepts astageOrderoverride, nothing wires it to a pipeline yet. - An approval bound to the new stage or amount. The signed approval every update needs names the deal, not the values written. Approvers are added through Manage approvers; with none trusted, every HubSpot read and update is refused.
Next
Connector Development Guide
The pattern this connector follows, and how to build the next one.
Policies and the decision
What
boundSignals closes, and why it’s here from this policy’s first
version.