Skip to content

fix(hubspot): match dotted subscription types and gate qualified_lead on lifecycle stage - #119

Merged
Atroci merged 1 commit into
mainfrom
fix/hubspot-qualified-lead-107
Oct 13, 2026
Merged

Atroci merged 1 commit into
mainfrom
fix/hubspot-qualified-lead-107

Conversation

@Atroci

@Atroci Atroci commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Closes #107.

Acceptance: a HubSpot contact.propertyChange is recorded as qualified_lead only when lifecyclestage moves to MQL or SQL (filterable); other property changes are acknowledged and ignored; contact.creation stays lead.

Bug: sanitize_key() stripped the dot from contact.propertyChange, so the qualified branch never matched and every HubSpot webhook (every property change included) was recorded as lead.

Change

  • Dot-preserving normalization; scalar-safe payload reads.
  • qualified_lead rule: propertyName = lifecyclestage, propertyValue in marketingqualifiedlead, salesqualifiedlead (case-insensitive), filter clicutcl_hubspot_qualified_lead_rule; non-scalar/empty filter values dropped, empty value never qualifies.
  • Other property changes -> is_ignored_event() (2xx, nothing recorded), same mechanism as Calendly cancellations.
  • Qualified event IDs include stage + HubSpot eventId (MQL then SQL, or re-entry, count; retries dedupe). Non-qualified IDs keep the pre-fix basis so in-flight retries still dedupe.

Behavior change: sites subscribed to property changes record fewer, qualified events instead of a lead per change. Documented in INTEGRATIONS.md.

Evidence: PHPUnit 250/250 (php 8.4, E_ALL, no notices), PHPCS clean; the 5 new HubSpot tests fail on the original adapter. Independent review: 7 risks + 1 question raised, all addressed (scalar guards, filter hardening, ID stability, re-entry, docs). node tools/qa/smoke.js passes.

🤖 Generated with Claude Code

… on lifecycle stage

HubSpotWebhookAdapter normalized subscriptionType with sanitize_key(), which
strips the dot, so contact.propertyChange never matched and every HubSpot
webhook was recorded as lead (#107). Fixing only the dot would have turned
every property change into a qualified_lead.

- Keep the dot when normalizing; read payload fields scalar-safely.
- qualified_lead only when propertyName is lifecyclestage and propertyValue is
  marketingqualifiedlead or salesqualifiedlead, case-insensitive; the rule is
  filterable via clicutcl_hubspot_qualified_lead_rule (non-scalar or empty
  values from the filter are dropped; an empty propertyValue never qualifies).
- Other contact.propertyChange payloads are ignored via is_ignored_event()
  (2xx, nothing recorded), like Calendly cancellations.
- Qualified event IDs include the stage and HubSpot's eventId, so MQL then
  SQL, or a later re-entry, are separate events while retries dedupe.
  Non-qualified events keep the pre-fix ID basis so in-flight retries still
  dedupe.
- Docs: INTEGRATIONS HubSpot mapping (incl. the behavior change for sites
  subscribed to property changes) and HOOKS-REFERENCE entry.

Closes #107.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T18:56:15.884839Z 46d9b7c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46d9b7c690

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +474 to +476
- Every other property change is acknowledged (2xx, `ignored`) without recording an event. Before #107 the dot in
`contact.propertyChange` was stripped, so every HubSpot webhook, including every property change, was recorded as
`lead`; sites subscribed to property changes now record fewer, qualified events.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the canonical REST webhook contract

This behavior change is documented only in the integration guide: docs/reference/REST-API.md:79-100 still describes ignored webhook events only in terms of Calendly and does not tell route consumers that non-qualifying HubSpot property changes now return a successful ignored response instead of recording a lead. Update the REST route contract with the new HubSpot qualification and response behavior.

AGENTS.md reference: AGENTS.md:L58-L60

Useful? React with 👍 / 👎.

@Atroci
Atroci merged commit 7ee35c2 into main Oct 13, 2026
14 checks passed
@Atroci
Atroci deleted the fix/hubspot-qualified-lead-107 branch October 13, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hubspot: dotted subscriptionType never matches, and qualified_lead mapping is unvalidated

1 participant