Skip to content

feat(hubspot): DB-free webhook receiver via 2-hop resolve#92

Merged
d-klotz merged 4 commits into
nextfrom
claude/hubspot-webhook-db-free
May 29, 2026
Merged

feat(hubspot): DB-free webhook receiver via 2-hop resolve#92
d-klotz merged 4 commits into
nextfrom
claude/hubspot-webhook-db-free

Conversation

@d-klotz

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

Copy link
Copy Markdown
Contributor

Summary

Aligns the HubSpot webhooks extension with the new useDatabase flag + binding-key route namespacing in friggframework/frigg#596. The receiver is now DB-free; the portalId → integrationId lookup moves to the queue worker via a two-hop resolve.

Flow

  1. HUBSPOT_WEBHOOK_RECEIVED (route, useDatabase: false): verify v3 signature → enqueue one HUBSPOT_WEBHOOK_RESOLVE per event with the raw payload. No DB access.
  2. HUBSPOT_WEBHOOK_RESOLVE (queue worker, DB): runs on a dry integration instance, reverse-looks up portalId → integrationId, re-enqueues HUBSPOT_WEBHOOK bound to that integration. Uses the framework's documented dry-instance path (createQueueWorker's no-id branch). Ambiguous resolution propagates (no cross-tenant misroute).
  3. HUBSPOT_WEBHOOK (queue worker, hydrated): consumer's bound handler runs with the correct per-account context. Default no-op.

Why

Sean's requirement: the initial webhook handler should not open a DB connection. With useDatabase: false the receiver Lambda doesn't even get the Prisma layer (faster cold start), and the only DB step (the lookup) lives in the worker where the DB is already available.

Dependency

Requires friggframework/frigg#596 (route namespacing + useDatabase). Until that core canary is published and this module's @friggframework/core is bumped to it, useDatabase/namespacing are inert (old core ignores them). Land #596 → publish canary → bump core here.

Test plan

  • 37 unit tests. Receiver: verifies signature, enqueues HUBSPOT_WEBHOOK_RESOLVE, asserts no DB lookup, 401 paths, skip-missing-portalId, empty batch. Resolve: lookup + re-enqueue, skip-on-no-match, skip-on-missing-portalId, ambiguity propagation. Bundle shape: useDatabase: false + 3 events.
  • End-to-end against the #596 canary in a consumer app (clockwork): two distinct namespaced routes, DB-free receiver, worker resolves + fires the bound handler.

🤖 Generated with Claude Code

📦 Published PR as canary version: Canary Versions

✨ Test out this PR locally via:

npm install @friggframework/api-module-hubspot@1.1.8-canary.92.86a6bc6.0
# or 
yarn add @friggframework/api-module-hubspot@1.1.8-canary.92.86a6bc6.0

Version

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

Changelog

🚀 Enhancement

  • @friggframework/api-module-hubspot
    • feat(hubspot): DB-free webhook receiver via 2-hop resolve #92 (@d-klotz)
  • @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
    • feat(hubspot): add Tier 3 webhooks extension bundle #91 (@d-klotz)

Authors: 1

Aligns the webhooks extension with frigg's new useDatabase flag + route
namespacing (friggframework/frigg#596). The receiver no longer touches
the database; the portal→integration lookup moves to the queue worker.

- index.js: declare `useDatabase: false`; add the HUBSPOT_WEBHOOK_RESOLVE
  queue event.
- handlers.js:
  - onHubSpotWebhookReceived (route, DB-free): verify v3 signature, then
    enqueue one HUBSPOT_WEBHOOK_RESOLVE per event carrying the raw payload.
    No portal lookup, no DB access.
  - onHubSpotWebhookResolve (worker, DB): reverse-look up portalId →
    integrationId on a dry instance, then re-enqueue HUBSPOT_WEBHOOK bound
    to that integration so the worker hydrates it and runs the consumer's
    handler with the correct per-account context. Uses the framework's
    documented dry-instance path; ambiguous resolution propagates.
  - onHubSpotWebhook: unchanged no-op default (consumer overrides).
- README: document the DB-free 2-hop flow + namespaced URL
  (/{bindingKey}/webhooks).

37 unit tests: receiver asserts NO db lookup + enqueues RESOLVE; resolve
handler asserts lookup + re-enqueue / skip / ambiguity propagation.

Depends on friggframework/frigg#596 (namespacing + useDatabase). Bump
@friggframework/core to that canary once published.

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: 0ee2063a09

ℹ️ 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/index.js
- Add a resolve-handler test asserting a clear throw when the consumer
  integration has no `commands` wired (createFriggCommands missing). The
  failure now lands in the worker (one hop from the misconfig), so pin the
  actionable error.
- README: document that HUBSPOT_WEBHOOK handlers must be idempotent —
  delivery is at-least-once (two SQS hops + HubSpot retry-on-non-2xx).

38 webhook tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz d-klotz added release prerelease This change is available in a prerelease. labels May 28, 2026
d-klotz and others added 2 commits May 28, 2026 19:27
Condense the receiver/resolve handler JSDoc and the bundle doc to the
essential "why". Drop @this/@param/@returns boilerplate (obvious from the
destructured args) and the inline comments that restated the code. No logic
changes — receiver stays DB-free, resolve still does the portal lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
frigg#596 (extension route namespacing + useDatabase) merged to next and
published as 2.0.0-next.89. Point the DB-free webhook extension at the
released version instead of the canary; lockfile resynced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-klotz
d-klotz merged commit 6cf4c34 into next May 29, 2026
4 checks passed
@d-klotz
d-klotz deleted the claude/hubspot-webhook-db-free branch May 29, 2026 17:35
@seanspeaks

Copy link
Copy Markdown
Contributor

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

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