-
Notifications
You must be signed in to change notification settings - Fork 2.8k
HubSpot track contact lifecycle changes #2947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…and improved schema validation
…, enhance logging, and improve lead tracking logic with lifecycle stage checks and settings integration
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughParses and centralizes HubSpot installation settings, updates webhook routing to branch by objectTypeId and subscriptionType, adds lifecycle-stage and deal-created gated lead paths, passes unified settings into lead and sale trackers, updates schemas/constants/UI, and registers a contact lifecyclestage propertyChange webhook. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant HS as HubSpot
participant Route as Webhook Route
participant Settings as Parsed Settings
participant Lead as trackHubSpotLeadEvent
participant Sale as trackHubSpotSaleEvent
participant API as HubSpot API
HS->>Route: Webhook (objectTypeId, subscriptionType, payload)
Route->>Settings: Extract installation.settings (leadTriggerEvent, leadLifecycleStageId, closedWonDealStageId)
alt objectTypeId == "0-1" (contact)
alt subscriptionType == "object.creation"
Route->>Lead: ({payload, workspace, authToken, settings})
else subscriptionType == "object.propertyChange"
Note over Route: propertyName may be lifecyclestage
Route->>Lead: ({payload, workspace, authToken, settings})
end
else objectTypeId == "0-3" (deal)
alt subscriptionType == "object.creation" and settings.leadTriggerEvent == "dealCreated"
Route->>Lead: ({payload, workspace, authToken, settings})
else
Route->>Sale: ({payload, workspace, authToken, settings})
end
end
Lead->>API: getContact(... lifecyclestage)
Sale->>API: getDeal / related lookups
Note over Lead,Sale: Track events using unified settings
sequenceDiagram
autonumber
participant UI as Settings UI
participant Action as updateHubSpotSettingsAction
participant Store as Installation Settings
UI->>UI: Parse defaults via hubSpotSettingsSchema
UI->>Action: Submit {leadTriggerEvent, leadLifecycleStageId, closedWonDealStageId}
Action->>Store: Merge fields into installation.settings
Store-->>UI: Persisted settings
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/integrations/hubspot/track-lead.ts (1)
73-123: Implement retry or ordered processing for HubSpot lead tracking
trackLead({ clickId: "", mode: "async" })throwsDubApiError(bad_request)if the customer record lacks aclickId.- Webhooks are delivered asynchronously, so final events can arrive before deferred tracking completes and will be dropped silently under
Promise.allSettled.- Add retry logic with exponential backoff on
code === "bad_request", or enqueue webhook events (e.g., Redis/SQS FIFO) to ensure ordered processing.
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (1)
107-108: Consider removing or gating console.log statements in production.Logging the full event payload and settings in production could:
- Expose sensitive contact/deal data in logs
- Increase log volume and costs
- Impact performance at scale
If these logs are needed for debugging, gate them behind a feature flag or environment check (e.g.,
process.env.NODE_ENV === "development").Apply this diff to gate logs behind a development check:
- console.log("[HubSpot] Event", event); - console.log("[HubSpot] Integration settings", settings); + if (process.env.NODE_ENV === "development") { + console.log("[HubSpot] Event", event); + console.log("[HubSpot] Integration settings", settings); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
apps/web/app/(ee)/api/hubspot/webhook/route.ts(1 hunks)apps/web/lib/integrations/hubspot/api.ts(1 hunks)apps/web/lib/integrations/hubspot/constants.ts(1 hunks)apps/web/lib/integrations/hubspot/schema.ts(4 hunks)apps/web/lib/integrations/hubspot/track-lead.ts(3 hunks)apps/web/lib/integrations/hubspot/track-sale.ts(2 hunks)apps/web/lib/integrations/hubspot/ui/settings.tsx(3 hunks)apps/web/lib/integrations/hubspot/update-hubspot-settings.ts(2 hunks)apps/web/scripts/create-integration.ts(1 hunks)packages/hubspot-app/src/app/webhooks/webhooks-hsmeta.json(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/hubspot-app/**/*-hsmeta.json
📄 CodeRabbit inference engine (packages/hubspot-app/CLAUDE.md)
packages/hubspot-app/**/*-hsmeta.json: Component configuration files must be named with the -hsmeta.json suffix
The uid field in -hsmeta.json files must be unique within the project
The type field in -hsmeta.json defines the component type and must be set correctly
Files:
packages/hubspot-app/src/app/webhooks/webhooks-hsmeta.json
🧠 Learnings (2)
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
PR: dubinc/dub#0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions in settings components
Applied to files:
apps/web/lib/integrations/hubspot/ui/settings.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
PR: dubinc/dub#0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Do not use React components from hubspot/ui-extensions/crm in settings components
Applied to files:
apps/web/lib/integrations/hubspot/ui/settings.tsx
🧬 Code graph analysis (6)
apps/web/lib/integrations/hubspot/track-sale.ts (3)
apps/web/lib/types.ts (1)
WorkspaceProps(198-214)apps/web/lib/integrations/types.ts (1)
HubSpotAuthToken(4-4)apps/web/lib/integrations/hubspot/schema.ts (1)
hubSpotSettingsSchema(18-37)
apps/web/lib/integrations/hubspot/track-lead.ts (4)
apps/web/lib/integrations/types.ts (1)
HubSpotAuthToken(4-4)apps/web/lib/integrations/hubspot/schema.ts (2)
hubSpotSettingsSchema(18-37)hubSpotLeadEventSchema(79-83)apps/web/lib/integrations/hubspot/api.ts (1)
HubSpotApi(7-134)apps/web/lib/api/conversions/track-lead.ts (1)
trackLead(29-363)
apps/web/lib/integrations/hubspot/update-hubspot-settings.ts (2)
apps/web/lib/integrations/hubspot/schema.ts (1)
hubSpotSettingsSchema(18-37)apps/web/lib/actions/safe-action.ts (1)
authActionClient(33-82)
apps/web/lib/integrations/hubspot/schema.ts (1)
apps/web/lib/integrations/hubspot/constants.ts (2)
LEAD_TRIGGER_EVENT_OPTIONS(11-14)HUBSPOT_OBJECT_TYPE_IDS(1-4)
apps/web/lib/integrations/hubspot/ui/settings.tsx (3)
apps/web/lib/types.ts (1)
InstalledIntegrationInfoProps(368-397)apps/web/lib/integrations/hubspot/schema.ts (1)
hubSpotSettingsSchema(18-37)apps/web/lib/integrations/hubspot/constants.ts (2)
HUBSPOT_DEFAULT_SETTINGS(6-9)LEAD_TRIGGER_EVENT_OPTIONS(11-14)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (3)
apps/web/lib/integrations/hubspot/schema.ts (1)
hubSpotSettingsSchema(18-37)apps/web/lib/integrations/hubspot/track-lead.ts (1)
trackHubSpotLeadEvent(10-177)apps/web/lib/integrations/hubspot/track-sale.ts (1)
trackHubSpotSaleEvent(8-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (14)
packages/hubspot-app/src/app/webhooks/webhooks-hsmeta.json (1)
27-32: LGTM! Webhook subscription correctly configured.The new subscription for lifecycle stage changes on contacts is properly structured and aligns with the schema updates in
schema.tsthat includelifecyclestagein the allowed property names.apps/web/lib/integrations/hubspot/api.ts (1)
58-58: LGTM! Property fetch aligns with schema.The addition of
lifecyclestageto the contact properties query correctly aligns with the updatedhubSpotContactSchemainschema.ts(line 49).apps/web/scripts/create-integration.ts (1)
10-12: LGTM! Integration metadata correctly updated.The integration name, slug, and description have been properly updated to reflect HubSpot.
apps/web/lib/integrations/hubspot/update-hubspot-settings.ts (2)
10-19: LGTM! Settings schema correctly extended.The schema extension and field extraction properly handle the new lead trigger settings (
leadTriggerEventandleadLifecycleStageId) alongside the existingclosedWonDealStageId.
43-44: LGTM! New settings fields correctly persisted.The settings update properly merges the new
leadTriggerEventandleadLifecycleStageIdfields into the existing settings object.apps/web/lib/integrations/hubspot/track-sale.ts (2)
8-17: LGTM! Settings parameter correctly added.The function signature update to accept a centralized
settingsparameter improves consistency and aligns with the new settings-driven approach across the HubSpot integration.
34-39: LGTM! Settings correctly used for validation.The validation logic correctly references
settings.closedWonDealStageIdfor both the comparison and error message.apps/web/lib/integrations/hubspot/constants.ts (1)
6-14: LGTM! Default settings and options well-defined.The centralized default settings and lead trigger options provide a good foundation for the configurable lead tracking feature. The defaults (
dealCreatedtrigger andclosedwonstage) are sensible choices.apps/web/lib/integrations/hubspot/ui/settings.tsx (3)
23-37: LGTM! Settings initialization correctly implemented.The settings are properly parsed with defaults using
hubSpotSettingsSchema, and the state is correctly initialized from the parsed values. This ensures the UI always has valid initial values.
84-110: LGTM! Lead trigger selection well-implemented.The
CardSelectorprovides clear options for when to track leads, and the reset logic (lines 105-107) correctly clearsleadLifecycleStageIdwhen switching to "New Deal Created" mode.
49-62: Verify client-side validation for required lifecycle stage ID.The form submission doesn't validate that
leadLifecycleStageIdis provided whenleadTriggerEventis set to "lifecycleStageReached". While the backend validation inhubSpotSettingsSchemamight handle this, consider adding client-side validation to improve UX.Example validation to add before submission:
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); if (!workspaceId) { return; } // Validate lifecycle stage ID when required if (leadTriggerEvent === "lifecycleStageReached" && !leadLifecycleStageId) { toast.error("Lead lifecycle stage ID is required when tracking by lifecycle stage."); return; } await executeAsync({ workspaceId, leadTriggerEvent, leadLifecycleStageId, closedWonDealStageId, }); };apps/web/lib/integrations/hubspot/schema.ts (3)
19-37: LGTM! Settings schema correctly extended.The new fields for lead trigger configuration are well-structured:
leadTriggerEventwith sensible default ("dealCreated") and clear enum optionsleadLifecycleStageIdfor lifecycle-based trackingclosedWonDealStageIdnow includes a default value ("closedwon")The descriptions clearly explain each field's purpose and when it applies.
49-49: LGTM! Contact schema correctly extended.The addition of
lifecyclestageto the contact properties aligns with the API fetch change inapi.ts(line 58) and supports the new lifecycle-based lead tracking.
79-83: LGTM! Lead event schema correctly updated.The
subscriptionTypeenum now supports both "object.propertyChange" (for lifecycle stage changes) and "object.creation" (for new contacts), which aligns with the expanded webhook subscriptions inwebhooks-hsmeta.json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/lib/integrations/hubspot/schema.ts(4 hunks)apps/web/lib/integrations/hubspot/track-lead.ts(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/lib/integrations/hubspot/schema.ts (1)
apps/web/lib/integrations/hubspot/constants.ts (1)
LEAD_TRIGGER_EVENT_OPTIONS(11-14)
apps/web/lib/integrations/hubspot/track-lead.ts (3)
apps/web/lib/integrations/types.ts (1)
HubSpotAuthToken(4-4)apps/web/lib/integrations/hubspot/schema.ts (2)
hubSpotSettingsSchema(18-37)hubSpotLeadEventSchema(79-83)apps/web/lib/integrations/hubspot/api.ts (1)
HubSpotApi(7-134)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
apps/web/lib/integrations/hubspot/schema.ts (1)
35-35: LGTM! Schema extensions support the new lifecycle tracking.The additions are correct:
- Default for
closedWonDealStageIdaligns with existing behaviorlifecyclestageproperty enables lifecycle stage tracking from contactssubscriptionTypeenum correctly allows both creation and property change events for leadsAlso applies to: 49-49, 81-81
apps/web/lib/integrations/hubspot/track-lead.ts (3)
14-14: LGTM! Function signature and payload parsing are correct.Adding the
settingsparameter and parsing the payload withhubSpotLeadEventSchemafollow best practices.Also applies to: 19-19, 25-26
71-123: LGTM! Deal creation tracking logic is correct.The conditional branching and tracking logic for deal creation events is well-structured:
- Properly checks for deal creation with the correct trigger setting
- Handles missing associations gracefully
- Uses
mode: "async"appropriately for secondary conversion events
125-181: Confirm webhook subscribes to lifecyclestage propertyChange
Ensure your HubSpot webhook subscription includesobject.propertyChangeevents on thelifecyclestageproperty for contacts (objectTypeId "0-1"), as the tracking logic depends on it.
…d trackSale functions, improving error handling for missing customers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/web/lib/integrations/hubspot/track-lead.ts (1)
122-127: Avoid"undefined"strings in contact namesUsing a template literal here produces values like
"undefined Doe"when HubSpot omits one half of the name. Mirror the earlier branch by filtering truthy parts before joining.- const trackLeadResult = await trackLead({ + const nameParts = [ + contactInfo.properties.firstname, + contactInfo.properties.lastname, + ].filter(Boolean); + + const trackLeadResult = await trackLead({ clickId: "", eventName: `Deal ${properties.dealstage}`, customerExternalId: customer.externalId!, - customerName: `${contactInfo.properties.firstname} ${contactInfo.properties.lastname}`, + customerName: nameParts.length ? nameParts.join(" ") : null,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/lib/integrations/hubspot/track-lead.ts(3 hunks)apps/web/lib/integrations/hubspot/track-sale.ts(3 hunks)apps/web/lib/integrations/hubspot/ui/settings.tsx(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/integrations/hubspot/ui/settings.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/lib/integrations/hubspot/track-lead.ts (4)
apps/web/lib/integrations/types.ts (1)
HubSpotAuthToken(4-4)apps/web/lib/integrations/hubspot/schema.ts (2)
hubSpotSettingsSchema(18-37)hubSpotLeadEventSchema(79-83)apps/web/lib/integrations/hubspot/api.ts (1)
HubSpotApi(7-134)apps/web/lib/api/conversions/track-lead.ts (1)
trackLead(29-363)
apps/web/lib/integrations/hubspot/track-sale.ts (2)
apps/web/lib/integrations/hubspot/schema.ts (1)
hubSpotSettingsSchema(18-37)apps/web/lib/api/conversions/track-sale.ts (1)
trackSale(43-302)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (1)
114-125: Add early validation for requiredleadLifecycleStageId.When
isLifecycleStageChangedis true,settings.leadLifecycleStageIdis required for matching the lifecycle stage (verified in track-lead.ts line 139). Without early validation here,trackHubSpotLeadEventwill be called unnecessarily and will only log an error internally before returning. Fail fast instead.Restructure to validate before calling the tracker:
- if (isContactCreated || isLifecycleStageChanged) { + if (isContactCreated) { await trackHubSpotLeadEvent({ payload: event, workspace, authToken, settings, }); + } else if (isLifecycleStageChanged) { + if (!settings.leadLifecycleStageId) { + console.error( + `[HubSpot] leadLifecycleStageId is required when leadTriggerEvent is "lifecycleStageReached" for portalId ${portalId}.`, + ); + return; + } + await trackHubSpotLeadEvent({ + payload: event, + workspace, + authToken, + settings, + }); }
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (1)
105-108: LGTM! Settings parsing and logging centralized effectively.Parsing the settings once at the start and logging both the event and settings improves observability and eliminates redundant parsing in downstream functions.
Consider using structured logging (e.g., req.log from Axiom) instead of
console.logfor better production observability:- console.log("[HubSpot] Event", event); - console.log("[HubSpot] Integration settings", settings); + req.log.info("[HubSpot] Processing event", { event, settings });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(ee)/api/hubspot/webhook/route.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (3)
apps/web/lib/integrations/hubspot/schema.ts (1)
hubSpotSettingsSchema(18-37)apps/web/lib/integrations/hubspot/track-lead.ts (1)
trackHubSpotLeadEvent(10-216)apps/web/lib/integrations/hubspot/track-sale.ts (1)
trackHubSpotSaleEvent(9-101)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (1)
128-155: LGTM! Deal event routing is well-structured.The logic correctly:
- Gates lead tracking on deal creation to when
leadTriggerEventis configured as"dealCreated"- Routes property changes to sale tracking
- Uses
else ifto ensure mutual exclusivity between deal creation and update handling- Passes unified settings to both tracking functions
|
@coderabbitai final review please |
|
@devkiran, I'll perform a final comprehensive review of your HubSpot contact lifecycle changes. ✅ Actions performedReview triggered.
|
Summary by CodeRabbit
New Features
Chores
Bug Fixes