Expands HubSpot's webhook trigger catalog from 2 slugs (HUBSPOT_CONTACT_CREATED, HUBSPOT_DEAL_STAGE_UPDATED) to 23 customer-facing slugs covering the full standard CRM object family + conversations inbox. Adds the missing app_level WebhookSpec (`apps/hubspot/trigger-config.ts`) so HubSpot triggers flow through Apollo's V2 setup_fields-based credential model, matching the Slack pattern.
| Family | New triggers |
|---|---|
| Contacts | Deleted, PropertyChanged |
| Companies | Created, Deleted, PropertyChanged |
| Deals | Created, Deleted, PropertyChanged |
| Tickets | Created, Deleted, PropertyChanged |
| Line items | Created, Deleted |
| Products | Created, Deleted |
| Quotes | Created, Deleted |
| Conversations | Created, Deleted, NewMessage, PropertyChanged |
Existing `HUBSPOT_CONTACT_CREATED` and `HUBSPOT_DEAL_STAGE_UPDATED` keep their slugs, payload shape, and behavior for back-compat with shipped `trigger_instances`.
Standard properties only — no custom properties on this catalog. The `*_PROPERTY_CHANGED` triggers accept a free-form `property_name` validated at setup via HubSpot's `hubspotDefined` flag. Custom properties get rejected with a clear error message pointing to the standard-property docs. Rationale: a marketplace app's 1,000 subscription cap is shared across thousands of customers; allowing arbitrary custom property names would exhaust it. The standard catalog (~525 properties total across all object types) is bounded regardless of customer count.
No hardcoded allowlist. HubSpot's per-portal API is the source of truth. Tier differences (Marketing/Sales/Service Hub specific properties) and future HubSpot-added standard properties work without code changes.
Credentials flow (Slack pattern). App-level webhooks need `client_secret` (HubSpot reuses it as the HMAC key — unusual; most providers issue a distinct signing secret), numeric `app_id`, and the developer-account `hapikey`. These are now declared as `setup_fields` in `trigger-config.ts` → stored encrypted on `webhook_endpoints.encryptedData` per project → merged into the `auth` dict by Apollo and read by Mercury via `auth["app_id"]` / `auth["developer_api_key"]`. Existing trigger_instances with legacy config keep working via `_resolve_app_credentials`' config fallback.
No silent polling fallback. If a customer wants something this catalog doesn't cover (custom properties, form submissions, list membership), they get an explicit error pointing to HubSpot's docs — never a stealth downgrade to polling.
| Source | Coverage | Notes |
|---|---|---|
| Zapier | 7 instant webhook triggers, all backed by `/webhooks/v3/` | + 16 polling triggers; their property-change triggers all single-property-per-Zap; `form.submission` uses an internal HubSpot mechanism unavailable to third parties |
| Pipedream | ~25 sources, all polling | No webhook sources for HubSpot at all — useful as a use-case coverage cross-check |
| n8n | 16 webhook event types via `HubspotTrigger.node.ts` | Same one-trigger-per-object + single-property selector shape Composio adopts |
| HubSpot docs | All eventType strings + payloads confirmed verbatim | v3 webhook subscriptions API; same envelope works on v4 (`POST /webhooks/v4/subscriptions`) but v4 is BETA + access-gated |
Out of scope this iteration (deferred, not blocked):
🤖 Generated with Claude Code