feat(hubspot): add Webhooks v3 Subscriptions API methods#89
Conversation
Adds three methods to the HubSpot Api class:
- listWebhookSubscriptions({ appId, developerApiKey })
- createWebhookSubscription({ appId, developerApiKey, subscriptionType, propertyName })
- deleteWebhookSubscription({ appId, developerApiKey, subscriptionId })
These hit /webhooks/v3/{appId}/subscriptions* and authenticate with the
developer-account API key as `Authorization: Bearer ${developerApiKey}`,
distinct from the per-portal OAuth bearer the rest of this Api class uses.
Endpoints accept appId + developerApiKey per-call so callers control
identity without mutating Api instance state.
Behavioural notes:
- createWebhookSubscription returns { status, data } so callers can
treat 409 (subscription already exists) as success without throwing.
- deleteWebhookSubscription tolerates 404 (subscription already gone).
- Other non-2xx responses throw with status + response body for context.
Refs: https://developers.hubspot.com/docs/api-reference/webhooks-webhooks-v3
10 unit tests covering happy-path, 404/409 idempotency, and error cases.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc17cee675
ℹ️ 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".
| { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ subscriptionDetails, enabled: true }), |
There was a problem hiding this comment.
Send the create-subscription payload with HubSpot field names
createWebhookSubscription posts { subscriptionDetails, enabled: true }, but the v3 subscriptions endpoint expects top-level fields like eventType, optional propertyName, and active. Because this method currently nests the event inside subscriptionDetails and uses enabled instead of active, valid calls can be rejected with 400 responses, so callers cannot reliably create webhook subscriptions through this API method.
Useful? React with 👍 / 👎.
Code reviewFound 1 issue:
api-module-library/packages/v1-ready/hubspot/api.js Lines 1029 to 1047 in fc17cee 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
The v3 Subscriptions endpoint expects flat top-level fields, not the
v4-Journal-style nested `subscriptionDetails`. Codex P1 review on PR
flagged this, verified against HubSpot's docs.
Before (rejected with 400 by HubSpot):
POST /webhooks/v3/{appId}/subscriptions
{ "subscriptionDetails": { "subscriptionType": "contact.creation" },
"enabled": true }
After:
POST /webhooks/v3/{appId}/subscriptions
{ "eventType": "contact.creation", "active": true }
Method parameter also renamed `subscriptionType` → `eventType` so the
api-module surface mirrors HubSpot's actual field names. The response
shape change (`eventType` at top level rather than nested under
`subscriptionDetails`) flows through to callers.
Tests updated.
Ref: #89 (comment)
|
Fixed in |
Summary
Adds three methods to the HubSpot
Apiclass for managing app-level webhook subscriptions:listWebhookSubscriptions({ appId, developerApiKey })createWebhookSubscription({ appId, developerApiKey, subscriptionType, propertyName })deleteWebhookSubscription({ appId, developerApiKey, subscriptionId })Why
Webhooks v3 Subscriptions is the API HubSpot apps use to subscribe to lifecycle / property-change events on contacts/companies/deals/etc. Right now consumers of
@friggframework/api-module-hubspothave to roll their own HTTP layer for these endpoints — defeating the api-module's purpose of being the single home for HubSpot HTTP surface.Reference: https://developers.hubspot.com/docs/api-reference/webhooks-webhooks-v3
Design notes
Auth is different from the rest of this Api class. Most endpoints use the per-portal OAuth bearer (
access_tokenset by the OAuth2Requester). Webhooks v3 Subscriptions is app-level, identified by the developer-account API key — sameAuthorization: Bearerheader but a different identity. Rather than mutate instance state (access_token) per-call, the new methods takeappId+developerApiKeyas parameters and usefetchdirectly with explicit headers. The endpoint URLs stay declared in theURLsmap for consistency.Note on
hapikey: HubSpot deprecated thehapikeyquery-param auth in Nov 2022. These methods use the modern Bearer-header form.409 / 404 semantics:
createWebhookSubscriptionreturns{ status, data }instead of throwing on 409, so callers can treat "already exists" as success in race conditions (e.g. two parallel handlers racing to register the same subscription).deleteWebhookSubscriptionsilently swallows 404 (subscription already gone — desired state achieved).${status} ${response body}for context.Test plan
packages/v1-ready/hubspot/tests/webhook-subscriptions.test.jsmockingglobal.fetch. Covers: happy-path GET/POST/DELETE, propertyName forwarding, 409 idempotency, 404 idempotency, error pathways.Downstream usage
This unblocks https://github.com/ClockworkRecruiting/integrations/pull/12 which adds webhook-driven ongoing sync between HubSpot and Clockwork via Frigg. Without these methods landing here, the downstream PR has to maintain its own HTTP surface for the same endpoints — exactly what api-modules exist to prevent.