Skip to content

feat(hubspot): add Tier 3 webhooks extension bundle#91

Merged
d-klotz merged 9 commits into
nextfrom
feat/hubspot-extension-webhooks
May 27, 2026
Merged

feat(hubspot): add Tier 3 webhooks extension bundle#91
d-klotz merged 9 commits into
nextfrom
feat/hubspot-extension-webhooks

Conversation

@d-klotz

@d-klotz d-klotz commented May 25, 2026

Copy link
Copy Markdown
Contributor

Adds hubspot.extensions.webhooks — the api-module half of the Tier 3 Integration Extensions work in friggframework/frigg#590 (ADR-EXTENSIONS in friggframework/frigg#589).

What's in the bundle

  • POST /webhooks receiver route bound to HUBSPOT_WEBHOOK_RECEIVED
  • Default HUBSPOT_WEBHOOK_RECEIVED handler: verifies v3 signature, iterates the inbound event array, resolves each portalId via findIntegrationByEntityExternalId(portalId, 'hubspot'), enqueues one HUBSPOT_WEBHOOK job per matched event, responds 200 { received, queued, skipped }
  • Default HUBSPOT_WEBHOOK handler: no-op (integrations override via binding.handlers)
  • findIntegrationByPortalId(integration, portalId) wrapper kept inside the api-module so HubSpot vocabulary stays out of core

Usage

const hubspot = require('@friggframework/api-module-hubspot');

class HubSpotIntegration extends IntegrationBase {
    static Definition = {
        name: 'hubspot',
        modules: { hubspot: { definition: hubspot.Definition } },
        extensions: {
            hubspotWebhooks: {
                extension: hubspot.extensions.webhooks,
                handlers: { HUBSPOT_WEBHOOK: 'onHubSpotEvent' },
            },
        },
    };

    async onHubSpotEvent({ data }) {
        // signature, portal lookup, queueing already done
        const { subscriptionType, objectId } = data.body;
        // ... business logic
    }
}

Signature verification

  • HMAC-SHA256 over method + fullUrl + body + timestamp, keyed by the app's client secret, base64-encoded
  • Reads X-HubSpot-Signature-V3 and X-HubSpot-Request-Timestamp (ms)
  • 5-minute skew window (rejects past and future timestamps)
  • X-Forwarded-Proto / X-Forwarded-Host honored for LB deployments; HUBSPOT_WEBHOOK_BASE_URL env var for full override
  • Prefers req.rawBody when middleware preserves it — JSON re-serialization would break the HMAC otherwise
  • Constant-time compare via crypto.timingSafeEqual
  • Client secret resolution: Definition.modules.hubspot.definition.env.client_secretprocess.env.HUBSPOT_CLIENT_SECRET

What's deferred (judgment calls)

  • Programmatic subscription registration at build/deploy time. Sean's "even better" from the Loom (auto-creating webhook subscriptions via the HubSpot API) is out of scope for this PR. Open PR feat(hubspot): add Webhooks v3 Subscriptions API methods #89 (feat/hubspot-webhook-subscriptions) is adding subscription-management primitives to api.js; once both land, a follow-up can call those primitives from a deploy hook. The receiver/handler/dispatch bundle stands on its own.
  • Custom queue/worker contributions. The extension declares no queues or workers — extension-contributed events flow through the existing per-integration queue worker per the Phase 2 TODO in integration-defined-workers.js.
  • One-event-per-message fan-out semantics. I queue one SQS message per HubSpot event (not per batch). HubSpot sends batches; downstream workers stay per-event for simpler retry semantics and DLQ correlation.

Framework contract note for #590 reviewer

The current IntegrationBase.queueWebhook in #590 hardcodes the top-level SQS message event to 'ON_WEBHOOK' and ignores data.event. EXTENSIONS.md and this PR pass event: 'HUBSPOT_WEBHOOK'. For end-to-end delivery to fire the bound HUBSPOT_WEBHOOK handler (not the default onWebhook no-op), queueWebhook needs to honor data.event when present. Flagging here so it can be addressed in #590 before merge.

Files

  • packages/v1-ready/hubspot/extensions/webhooks/index.js — bundle export
  • packages/v1-ready/hubspot/extensions/webhooks/signature-verifier.js — HMAC v3 logic
  • packages/v1-ready/hubspot/extensions/webhooks/handlers.js — the two event handlers
  • packages/v1-ready/hubspot/extensions/webhooks/lookup.jsfindIntegrationByPortalId wrapper
  • packages/v1-ready/hubspot/index.js — adds extensions: { webhooks } alongside existing Api / Config / Definition
  • packages/v1-ready/hubspot/tests/extensions/webhooks.test.js — 34 unit tests

Does not touch packages/v1-ready/hubspot/api.js — PR #89's territory.

Test plan

  • npx jest tests/extensions/webhooks.test.js — 34 passed
  • npx jest tests/auther.test.js — existing 5 passed, 5 skipped (live tests intentionally skipped without HubSpot creds)
  • node -e "require('./index')" — bundle exports cleanly
  • End-to-end smoke test with a deployed integration (requires framework #590 merged + queueWebhook honoring data.event — see note above)
📦 Published PR as canary version: Canary Versions

✨ Test out this PR locally via:

npm install @friggframework/api-module-microsoft-teams@1.1.6-canary.91.3686b18.0
npm install @friggframework/api-module-slack@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-42matters@1.1.5-canary.91.3686b18.0
npm install @friggframework/api-module-asana@1.1.6-canary.91.3686b18.0
npm install @friggframework/api-module-attio@1.0.1-canary.91.3686b18.0
npm install @friggframework/api-module-clio@1.0.1-canary.91.3686b18.0
npm install @friggframework/api-module-connectwise@1.0.5-canary.91.3686b18.0
npm install @friggframework/api-module-contentful@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-contentstack@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-crossbeam@1.0.1-canary.91.3686b18.0
npm install @friggframework/api-module-deel@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-frigg-scale-test@0.1.1-canary.91.3686b18.0
npm install @friggframework/api-module-frontify@1.3.1-canary.91.3686b18.0
npm install @friggframework/api-module-google-calendar@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-google-drive@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-helpscout@0.1.3-canary.91.3686b18.0
npm install @friggframework/api-module-hubspot@1.1.8-canary.91.3686b18.0
npm install @friggframework/api-module-ironclad@1.0.2-canary.91.3686b18.0
npm install @friggframework/api-module-linear@1.1.4-canary.91.3686b18.0
npm install @friggframework/api-module-pipedrive@2.0.1-canary.91.3686b18.0
npm install @friggframework/api-module-salesforce@1.0.3-canary.91.3686b18.0
npm install @friggframework/api-module-stripe@1.0.1-canary.91.3686b18.0
npm install @friggframework/api-module-unbabel@1.1.6-canary.91.3686b18.0
npm install @friggframework/api-module-unbabel-projects@1.0.3-canary.91.3686b18.0
npm install @friggframework/api-module-zoho-crm@1.0.3-canary.91.3686b18.0
npm install @friggframework/api-module-zoom@1.0.1-canary.91.3686b18.0
# or 
yarn add @friggframework/api-module-microsoft-teams@1.1.6-canary.91.3686b18.0
yarn add @friggframework/api-module-slack@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-42matters@1.1.5-canary.91.3686b18.0
yarn add @friggframework/api-module-asana@1.1.6-canary.91.3686b18.0
yarn add @friggframework/api-module-attio@1.0.1-canary.91.3686b18.0
yarn add @friggframework/api-module-clio@1.0.1-canary.91.3686b18.0
yarn add @friggframework/api-module-connectwise@1.0.5-canary.91.3686b18.0
yarn add @friggframework/api-module-contentful@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-contentstack@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-crossbeam@1.0.1-canary.91.3686b18.0
yarn add @friggframework/api-module-deel@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-frigg-scale-test@0.1.1-canary.91.3686b18.0
yarn add @friggframework/api-module-frontify@1.3.1-canary.91.3686b18.0
yarn add @friggframework/api-module-google-calendar@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-google-drive@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-helpscout@0.1.3-canary.91.3686b18.0
yarn add @friggframework/api-module-hubspot@1.1.8-canary.91.3686b18.0
yarn add @friggframework/api-module-ironclad@1.0.2-canary.91.3686b18.0
yarn add @friggframework/api-module-linear@1.1.4-canary.91.3686b18.0
yarn add @friggframework/api-module-pipedrive@2.0.1-canary.91.3686b18.0
yarn add @friggframework/api-module-salesforce@1.0.3-canary.91.3686b18.0
yarn add @friggframework/api-module-stripe@1.0.1-canary.91.3686b18.0
yarn add @friggframework/api-module-unbabel@1.1.6-canary.91.3686b18.0
yarn add @friggframework/api-module-unbabel-projects@1.0.3-canary.91.3686b18.0
yarn add @friggframework/api-module-zoho-crm@1.0.3-canary.91.3686b18.0
yarn add @friggframework/api-module-zoom@1.0.1-canary.91.3686b18.0

Version

Published prerelease version: @friggframework/api-module-hubspot@2.0.0-next.4

Changelog

🚀 Enhancement

  • @friggframework/api-module-hubspot
    • feat(hubspot): add Tier 3 webhooks extension bundle #91 (@d-klotz)

🐛 Bug Fix

  • @friggframework/api-module-microsoft-teams, @friggframework/api-module-slack, @friggframework/api-module-42matters, @friggframework/api-module-asana, @friggframework/api-module-attio, @friggframework/api-module-clio, @friggframework/api-module-connectwise, @friggframework/api-module-contentful, @friggframework/api-module-contentstack, @friggframework/api-module-crossbeam, @friggframework/api-module-deel, @friggframework/api-module-frigg-scale-test, @friggframework/api-module-frontify, @friggframework/api-module-google-calendar, @friggframework/api-module-google-drive, @friggframework/api-module-helpscout, @friggframework/api-module-hubspot, @friggframework/api-module-ironclad, @friggframework/api-module-linear, @friggframework/api-module-pipedrive, @friggframework/api-module-salesforce, @friggframework/api-module-stripe, @friggframework/api-module-unbabel-projects, @friggframework/api-module-unbabel, @friggframework/api-module-zoho-crm, @friggframework/api-module-zoom
    • fix(hubspot): coerce portalId to String for externalId #90 (@d-klotz)

Authors: 1

Adds `hubspot.extensions.webhooks` matching the Tier 3 Integration
Extension contract defined in friggframework/frigg#590. Integration
consumers wire it in via `static Definition.extensions`:

    extensions: {
        hubspotWebhooks: {
            extension: hubspot.extensions.webhooks,
            handlers: { HUBSPOT_WEBHOOK: 'onHubSpotEvent' },
        },
    },

The bundle exposes one receiver route (POST /webhooks) plus two
events. The default `HUBSPOT_WEBHOOK_RECEIVED` handler verifies the
HubSpot v3 signature (HMAC SHA-256 over method+uri+body+timestamp,
client-secret keyed, base64-encoded, 5-minute skew), iterates the
inbound event array, resolves each event's portalId to a Frigg
integration via the platform-neutral `findIntegrationByEntityExternalId`
helper, and enqueues `HUBSPOT_WEBHOOK` jobs per matched event.

A thin `findIntegrationByPortalId(integration, portalId)` wrapper
lives inside the extension so HubSpot vocabulary stays in the
api-module — core stays platform-neutral.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@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: 46c07d3607

ℹ️ 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 thread packages/v1-ready/hubspot/extensions/webhooks/handlers.js Outdated
Comment thread packages/v1-ready/hubspot/extensions/webhooks/signature-verifier.js
@d-klotz

d-klotz commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Code review

Found 2 issues:

  1. Per-event SQS dispatch is sequential — the receiver loops events with await findIntegrationByPortalId(...) then await this.queueWebhook(...), one event at a time inside the synchronous HTTP handler. HubSpot batches can carry dozens of events; with cold-Lambda DB lookup (~20-50ms) + SQS send (~10-30ms) per iteration, a sizeable batch can blow past HubSpot's 5s response budget and trigger a full-batch retry — duplicating every event that was already queued before the timeout. Promise.all(events.map(...)) (or allSettled if partial failure is acceptable) is the trivial fix and also removes the manual queued/skipped counter mutation. (bug due to sequential await in onHubSpotWebhookReceived for-loop)

let queued = 0;
let skipped = 0;
for (let i = 0; i < events.length; i++) {
const evt = events[i];
const portalId = evt && evt.portalId;
const subscriptionType = evt && evt.subscriptionType;
if (portalId === undefined || portalId === null) {
console.warn(
`[hubspot-webhooks] event[${i}] missing portalId (subscriptionType=${subscriptionType}); skipping`
);
skipped++;
continue;
}
// Intentionally do not catch — ambiguous resolution is a cross-tenant
// routing risk and the core helper throws by design.
const integrationId = await findIntegrationByPortalId(this, portalId);
if (!integrationId) {
skipped++;
continue;
}
await this.queueWebhook({
integrationId,
body: evt,
event: EVENT_NAME_DISPATCH,
});
queued++;
}

  1. resolveClientSecret JSDoc lists a path the implementation never reads — the docstring says precedence 1 is Definition.env.client_secret on the integration class, but the body traverses integration.constructor.Definition.modules.<key>.definition.env.client_secret (the api-module's nested env block, not the integration class's top-level Definition.env). A consumer following the docstring and setting Definition.env.client_secret on their Integration class would have it silently ignored, falling through to process.env.HUBSPOT_CLIENT_SECRET. The accompanying tests only exercise the nested-module path and the env-var fallback — never the path the JSDoc actually documents. Either fix the docstring to reflect the nested-module traversal, or fix the function to honor the documented top-level path. While there, consider whether the Object.values(modules).find(...) traversal is the right design at a security boundary — if any other module in the same Integration ever carries env.client_secret, key-insertion order silently determines which secret verifies HubSpot signatures. (bug due to JSDoc lines 9-13 vs. code body lines 18-28)

/**
* Resolve the HubSpot app client secret used to sign webhook payloads.
*
* Precedence:
* 1. Definition.env.client_secret on the integration class (preferred — same
* env block the api-module already reads for OAuth)
* 2. process.env.HUBSPOT_CLIENT_SECRET (escape hatch when extensions are
* used outside a full integration class context, e.g. tests)
*
* @param {Object} integration - The IntegrationBase instance running the handler.
* @returns {string|undefined} The client secret, or undefined if neither source has it.
*/
function resolveClientSecret(integration) {
const fromDefinition =
integration &&
integration.constructor &&
integration.constructor.Definition &&
integration.constructor.Definition.modules &&
Object.values(integration.constructor.Definition.modules)
.map((m) => m && m.definition && m.definition.env)
.find((env) => env && env.client_secret);
return fromDefinition?.client_secret || process.env.HUBSPOT_CLIENT_SECRET;
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Four targeted fixes from Daniel's review:

1. lookup.js — source the module name from `definition.js` instead of
   hardcoding `'hubspot'`. A rename of the api-module now flows through
   automatically via `Definition.moduleName`.

2. lookup.js — switch from `integration.findIntegrationByEntityExternalId`
   to `integration.commands.findIntegrationByEntityExternalId`. The
   commands surface is the canonical access pattern for cross-cutting
   lookups in Frigg; the framework-side PR exposes the same use case
   there. Error message + tests updated to match the new contract.

3. extensions/webhooks/index.js — replace the vague "EXTENSIONS.md in the
   framework repo" pointer with a concrete URL to the file on the
   `next` branch of friggframework/frigg.

4. handlers.js — inline `'HUBSPOT_WEBHOOK'` at the single call site and
   drop the `EVENT_NAME_DISPATCH` const + export. Unnecessary
   indirection for a string used once.

Tests still green (68 passing across the two affected suites).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz

d-klotz commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 387ef00 addressing the four review points:

  1. lookup.js module name — now sourced from definition.js (Definition.moduleName) instead of the hardcoded 'hubspot' string. A rename of the api-module flows through automatically.

  2. lookup.js integration access pattern — switched to integration.commands.findIntegrationByEntityExternalId(...). Verified findIntegrationByEntityExternalId is not currently exposed in friggCommands, so I added it (plus listIntegrationsByEntityExternalId) on the framework side: see friggframework/frigg@b2cd5e2 — the logic moves into two use cases (FindIntegrationByEntityExternalIdUseCase, ListIntegrationsByEntityExternalIdUseCase) and is exposed via createIntegrationCommands. The IntegrationBase instance methods are gone — single source of truth, canonical access path.

  3. Extension index.js doc link — replaced the vague "EXTENSIONS.md in the framework repo" reference with the concrete URL: https://github.com/friggframework/frigg/blob/next/packages/core/integrations/EXTENSIONS.md.

  4. Inline event name — dropped the EVENT_NAME_DISPATCH const and its export, inlined 'HUBSPOT_WEBHOOK' at the single call site in handlers.js.

Tests rewritten to mock integration.commands.findIntegrationByEntityExternalId instead of the (now removed) instance method. 68 tests passing across the two affected suites.

Note: this PR now depends on the framework-side change in friggframework/frigg#590 — they should land together (or #590 first).

Comment thread packages/v1-ready/hubspot/extensions/webhooks/handlers.js Outdated
d-klotz and others added 3 commits May 25, 2026 15:05
…ectly

Daniel's follow-up: why not just read process.env.HUBSPOT_CLIENT_SECRET
directly?

There is no good reason. resolveClientSecret was doing a fragile
Object.values(Definition.modules) traversal that:
  - silently picked the first module with env.client_secret if more
    than one was present — a key-order assumption in a security path
  - duplicated the env-var read at the bottom of the function anyway
  - had a JSDoc precedence section that didn't actually match the code

Replaced with a direct `process.env.HUBSPOT_CLIENT_SECRET` read at the
single call site. Deleted the helper + its export, simplified the test
integration mock (no more constructor.Definition.modules scaffolding),
and replaced the "falls back to env var" test with one that asserts
401 when the env var is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the sequential-dispatch finding from the earlier review.

Before: a for-loop awaited findIntegrationByPortalId then queueWebhook
per event. A 50-event HubSpot batch took 50 × (DB lookup + SQS send)
inside the synchronous HTTP receiver — realistic batch sizes could
exceed HubSpot's 5s response budget and trigger a full-batch retry,
duplicating every event that had already been queued before the timeout.
On top of that, if a lookup midway through the batch threw ambiguous,
the events ahead of it were already queued; HubSpot retried, those
events were queued again.

After: two-phase. Phase 1 resolves every portalId in parallel
(Promise.all). Phase 2 enqueues every match in parallel. If any Phase 1
lookup throws (ambiguous resolution → cross-tenant routing risk), it
bubbles before any queueWebhook fires, so the batch is all-or-nothing
on the queue side. Same fail-loud semantics on ambiguity; no partial
state for HubSpot's retry to duplicate.

Side effect: the manual queued/skipped counters are gone — we just
filter + take .length on the resolutions array.

Two new tests lock the behavior in:
  - "queues zero events when any one resolution is ambiguous"
  - "resolves and queues events in parallel rather than serially"
    (asserts all three lookups + all three queue writes are in flight
     concurrently at some point)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Graphite review flagged that the empty-array early-return responded
{ received: 0, queued: 0 } while the normal path returned
{ received, queued, skipped } — a client referencing .skipped on the
empty case would see undefined.

Rather than patching the early-return to add skipped: 0, delete the
branch entirely. Promise.all([]) → [], filter(Boolean) → [], so the
normal path handles the empty case as a degenerate run and emits the
same shape ({ received: 0, queued: 0, skipped: 0 }). One less branch,
one less response-shape definition, one less inconsistency to remember.

Updated the empty-batch test to assert the unified shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz
d-klotz requested a review from seanspeaks May 25, 2026 21:27

@seanspeaks seanspeaks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Largely a LGTM based on reviewing the reviews

@d-klotz d-klotz added release released This issue/pull request has been released. prerelease This change is available in a prerelease. labels May 27, 2026
Expands the api-module README from a stub to explain the Tier 3
webhooks extension added in this PR:
- the app-level vs account-level problem it solves
- the concern/identity split (signature → app secret, routing →
  portalId lookup, business logic → per-portal credentials)
- how to bind it on an integration (Definition.extensions + commands)
- the HUBSPOT_CLIENT_SECRET requirement
- what the bundle contributes (routes + the two events)

Also closes the unterminated docs-site markdown link in the intro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz d-klotz removed the released This issue/pull request has been released. label May 27, 2026
The webhooks extension depends on the friggCommands surface added in
friggframework/frigg#590 (integration.commands.findIntegrationByEntityExternalId).
Pin to the canary published from that PR's commit b2cd5e2 so the
api-module installs and tests against the matching framework build:

  @friggframework/core 2.0.0--canary.590.b2cd5e2.0

Lockfile regenerated — the v2 core pulls in the AWS SDK v3 transitive
tree that v1.1.2 did not carry.

When #590 lands and a stable @friggframework/core 2.x ships, swap this
canary for the released range.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz
d-klotz changed the base branch from main to next May 27, 2026 16:21
d-klotz and others added 2 commits May 27, 2026 13:24
Retarget PR #91 from main to next. Conflicts resolved:

- packages/v1-ready/hubspot/package.json: kept the canary
  @friggframework/core 2.0.0--canary.590.b2cd5e2.0 over next's
  ^2.0.0-next.16 — the canary carries framework PR #590's friggCommands
  (commands.findIntegrationByEntityExternalId) that this extension's
  lookup.js depends on; next.16 predates #590. Swap for a stable next
  core once #590 lands and publishes.
- package-lock.json: regenerated from next's lockfile against the merged
  manifest rather than hand-merged.

Side benefit: next already fixes the lerna packages glob
(packages/** vs the stale packages/* on main), so `lerna ls` now finds
all 26 packages and the release job can publish again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #590 (Tier 3 extensions + friggCommands reverse-lookup) is merged and
released as 2.0.0-next.88. Swap the temporary canary pin
(2.0.0--canary.590.b2cd5e2.0) for the released range ^2.0.0-next.88.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz
d-klotz merged commit fd7310a into next May 27, 2026
3 checks passed
@d-klotz
d-klotz deleted the feat/hubspot-extension-webhooks branch May 27, 2026 19:55
@seanspeaks

Copy link
Copy Markdown
Contributor

🚀 PR was released in @friggframework/api-module-hubspot@2.0.0-next.4 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prerelease This change is available in a prerelease. release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants