@particle-academy/hubspot-js 0.3.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +218 -1
- package/art/fancified.svg +26 -0
- package/dist/chunk-SZT3VK7Z.js +1146 -0
- package/dist/chunk-SZT3VK7Z.js.map +1 -0
- package/dist/flow.cjs +1340 -13
- package/dist/flow.cjs.map +1 -1
- package/dist/flow.d.cts +19 -1
- package/dist/flow.d.ts +19 -1
- package/dist/flow.js +275 -5
- package/dist/flow.js.map +1 -1
- package/dist/index.cjs +1081 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +637 -2
- package/dist/index.d.ts +637 -2
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/dist/chunk-6HO2IP5Q.js +0 -104
- package/dist/chunk-6HO2IP5Q.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,67 @@ The four packages share one version, because they are generated from one
|
|
|
7
7
|
`provider/` definition and a version that meant something different in each
|
|
8
8
|
would be a version nobody could reason about.
|
|
9
9
|
|
|
10
|
+
## [0.5.0] — 2026-11-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`crm_event`: HubSpot's CRM webhooks, verified.** HubSpot pushes a BATCH of
|
|
15
|
+
events (deal, contact and company created, property changed, deleted,
|
|
16
|
+
merged, restored, re-associated; which ones is set in the HubSpot app) to
|
|
17
|
+
the app's target URL. Each delivery is verified with **signature v3**
|
|
18
|
+
before anything reads it: base64 HMAC-SHA256 of method + URI + body +
|
|
19
|
+
timestamp under the app's `clientSecret`. The timestamp
|
|
20
|
+
(`X-HubSpot-Request-Timestamp`) is in MILLISECONDS, with a 5-minute window,
|
|
21
|
+
and the URI has only its query percent-decoded. HubSpot's own published
|
|
22
|
+
example verifies in TypeScript, PHP and Python; a changed byte, another
|
|
23
|
+
method, a late delivery and a missing request line are each refused.
|
|
24
|
+
- **The host must pass the request line.** `verifyHubspotDelivery({raw,
|
|
25
|
+
headers, method, url})` (js), `CrmEvent::verifyDelivery($raw, $headers,
|
|
26
|
+
$secret, method: …, url: …)` (php) and `verify_delivery(raw, headers,
|
|
27
|
+
secret, method=…, url=…)` (python) take the URL exactly as it arrived.
|
|
28
|
+
Without them a delivery is refused by name, never verified with a gap.
|
|
29
|
+
- **Routing is DECLARED** (`route: {by: "portalId", path: "[].portalId"}`,
|
|
30
|
+
on the descriptor, as `ROUTE`, and in the registry index). One URL serves
|
|
31
|
+
every portal that installed the app, so route each event by `portalId`
|
|
32
|
+
ONLY after the signature verifies, and only to a connection minted when
|
|
33
|
+
that portal installed. Drop an unmapped portal with a 2xx and a log line,
|
|
34
|
+
and create nothing.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **Requires `fancy-connector-core` ≥ 0.12.0**, which verifies a signed
|
|
39
|
+
request line and millisecond timestamps.
|
|
40
|
+
|
|
41
|
+
## [0.4.0] — 2026-11-01
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **The CRM core, read and write: 17 new actions.** For deals, contacts and
|
|
46
|
+
companies:
|
|
47
|
+
- `*_search`: property `filterGroups` and `sorts` (nested JSON), a text
|
|
48
|
+
`query`, the properties to return, and HubSpot's `after` cursor;
|
|
49
|
+
- `*_list`: page through every record, with `after` in and
|
|
50
|
+
`paging.next.after` out;
|
|
51
|
+
- `*_get`: one record, with `associations` when asked;
|
|
52
|
+
- `deal_create` and `company_create`;
|
|
53
|
+
- `*_update`: a PATCH of the given properties, declared `idempotent`.
|
|
54
|
+
|
|
55
|
+
`contact_create` is unchanged, because saved workflows depend on its
|
|
56
|
+
fields. Alongside them: `owner_list` (owner ids are NOT user ids; both are
|
|
57
|
+
published), `pipeline_list` (every deal pipeline with its stages in order)
|
|
58
|
+
and `property_list` (the property definitions of deals, contacts or
|
|
59
|
+
companies). Record ids are numeric STRINGS in every runtime.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- **Scopes:** the CRM core's read and write scopes for the three objects,
|
|
64
|
+
plus `crm.objects.owners.read` and `crm.schemas.*.read`. An installation made
|
|
65
|
+
under 0.3.x granted only `crm.objects.contacts.write`, so it **must be
|
|
66
|
+
re-authorised** before any new action works.
|
|
67
|
+
- **Drift now checks every action against HubSpot's own v3 documents:**
|
|
68
|
+
Contacts, Companies, Deals, Owners, Pipelines and Properties. That needs
|
|
69
|
+
the six documents, because HubSpot publishes the CRM one area per document.
|
|
70
|
+
|
|
10
71
|
## [0.3.4] — 2026-09-12
|
|
11
72
|
|
|
12
73
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# HubSpot
|
|
2
2
|
|
|
3
|
+
[](https://particle.academy)
|
|
4
|
+
|
|
3
5
|
HubSpot for [fancy-flow][flow] — as **four imported, versioned packages**, one
|
|
4
6
|
per runtime. Not vendored source: a copy cannot be upgraded, and third-party APIs
|
|
5
7
|
change.
|
|
@@ -50,7 +52,7 @@ HubSpot uses OAuth2 (authorization_code). The package DECLARES the exchange; the
|
|
|
50
52
|
|
|
51
53
|
- **Authorize URL** — https://app.hubspot.com/oauth/authorize
|
|
52
54
|
- **Token URL** — https://api.hubapi.com/oauth/v1/token
|
|
53
|
-
- **Scopes** — `crm.objects.contacts.write`
|
|
55
|
+
- **Scopes** — `crm.objects.contacts.read`, `crm.objects.contacts.write`, `crm.objects.companies.read`, `crm.objects.companies.write`, `crm.objects.deals.read`, `crm.objects.deals.write`, `crm.objects.owners.read`, `crm.schemas.contacts.read`, `crm.schemas.companies.read`, `crm.schemas.deals.read`
|
|
54
56
|
- **Access token lifetime** — 1800 seconds (30 minutes). A host that never refreshes works all afternoon and is broken by morning.
|
|
55
57
|
|
|
56
58
|
The refresh tokens do **not** rotate: the same one is reusable, so a refresh may safely be retried and may run concurrently. Stated rather than assumed, because the opposite — a provider that spends the token and revokes the grant on a replay — looks identical until it happens.
|
|
@@ -65,6 +67,67 @@ HubSpot has a test estate on the same host, reached with credentials from a SEPA
|
|
|
65
67
|
|
|
66
68
|
### Actions
|
|
67
69
|
|
|
70
|
+
#### `company_create` — Create company
|
|
71
|
+
|
|
72
|
+
Create a company in HubSpot.
|
|
73
|
+
|
|
74
|
+
`POST /crm/v3/objects/companies` · **unsafe to replay** — a retried durable run does it TWICE
|
|
75
|
+
|
|
76
|
+
| Input | Required | What it is |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `properties` | yes | HubSpot property name → value for the new company. Values are sent as strings, which is how HubSpot stores every property. |
|
|
79
|
+
|
|
80
|
+
#### `company_get` — Get company
|
|
81
|
+
|
|
82
|
+
Read one company, with its associations when asked.
|
|
83
|
+
|
|
84
|
+
`GET /crm/v3/objects/companies/{companyId}` · reads only — safe to replay
|
|
85
|
+
|
|
86
|
+
| Input | Required | What it is |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `companyId` | yes | The company's HubSpot id (a numeric string). |
|
|
89
|
+
| `properties` | no | Comma-separated property names to return. HubSpot returns a small default set otherwise. |
|
|
90
|
+
| `associations` | no | Comma-separated object types whose associated ids to return. |
|
|
91
|
+
|
|
92
|
+
#### `company_list` — List companies
|
|
93
|
+
|
|
94
|
+
Page through every company in the portal.
|
|
95
|
+
|
|
96
|
+
`GET /crm/v3/objects/companies` · reads only — safe to replay
|
|
97
|
+
|
|
98
|
+
| Input | Required | What it is |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `limit` | no | How many records per page, 1 to 100. HubSpot's own default is 10. |
|
|
101
|
+
| `after` | no | The cursor from the previous page's paging.next.after. Leave empty for the first page. |
|
|
102
|
+
| `properties` | no | Comma-separated property names to return. HubSpot returns a small default set otherwise. |
|
|
103
|
+
| `associations` | no | Comma-separated object types whose associated ids to return. |
|
|
104
|
+
|
|
105
|
+
#### `company_search` — Search companies
|
|
106
|
+
|
|
107
|
+
Find companies by property filters, sorts or a text query.
|
|
108
|
+
|
|
109
|
+
`POST /crm/v3/objects/companies/search` · reads only — safe to replay
|
|
110
|
+
|
|
111
|
+
| Input | Required | What it is |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `filterGroups` | no | HubSpot's filterGroups: groups are ORed, filters inside a group are ANDed. Example: [{"filters":[{"propertyName":"amount","operator":"GT","value":"1000"}]}]. |
|
|
114
|
+
| `sorts` | no | Example: [{"propertyName":"createdate","direction":"DESCENDING"}]. |
|
|
115
|
+
| `query` | no | Searches the object's default text properties. |
|
|
116
|
+
| `properties` | no | Comma-separated property names to return. |
|
|
117
|
+
| `limit` | no | 1 to 200. |
|
|
118
|
+
| `after` | no | The cursor from the previous page's paging.next.after. |
|
|
119
|
+
|
|
120
|
+
#### `company_update` — Update company
|
|
121
|
+
|
|
122
|
+
Change properties on an existing company.
|
|
123
|
+
|
|
124
|
+
`PATCH /crm/v3/objects/companies/{companyId}` · idempotent — safe to replay
|
|
125
|
+
|
|
126
|
+
| Input | Required | What it is |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `companyId` | yes | The company's HubSpot id (a numeric string). |
|
|
129
|
+
| `properties` | yes | HubSpot property name → value for the company. Values are sent as strings, which is how HubSpot stores every property. |
|
|
130
|
+
|
|
68
131
|
#### `contact_create` — Create contact
|
|
69
132
|
|
|
70
133
|
Create a contact in HubSpot.
|
|
@@ -80,6 +143,160 @@ Create a contact in HubSpot.
|
|
|
80
143
|
| `phone` | no | Phone |
|
|
81
144
|
| `lifecycleStage` | no | HubSpot's own stage names. A value it does not recognise is refused at write time, not at setup. |
|
|
82
145
|
|
|
146
|
+
#### `contact_get` — Get contact
|
|
147
|
+
|
|
148
|
+
Read one contact, with its associations when asked.
|
|
149
|
+
|
|
150
|
+
`GET /crm/v3/objects/contacts/{contactId}` · reads only — safe to replay
|
|
151
|
+
|
|
152
|
+
| Input | Required | What it is |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `contactId` | yes | The contact's HubSpot id (a numeric string). |
|
|
155
|
+
| `properties` | no | Comma-separated property names to return. HubSpot returns a small default set otherwise. |
|
|
156
|
+
| `associations` | no | Comma-separated object types whose associated ids to return. |
|
|
157
|
+
|
|
158
|
+
#### `contact_list` — List contacts
|
|
159
|
+
|
|
160
|
+
Page through every contact in the portal.
|
|
161
|
+
|
|
162
|
+
`GET /crm/v3/objects/contacts` · reads only — safe to replay
|
|
163
|
+
|
|
164
|
+
| Input | Required | What it is |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `limit` | no | How many records per page, 1 to 100. HubSpot's own default is 10. |
|
|
167
|
+
| `after` | no | The cursor from the previous page's paging.next.after. Leave empty for the first page. |
|
|
168
|
+
| `properties` | no | Comma-separated property names to return. HubSpot returns a small default set otherwise. |
|
|
169
|
+
| `associations` | no | Comma-separated object types whose associated ids to return. |
|
|
170
|
+
|
|
171
|
+
#### `contact_search` — Search contacts
|
|
172
|
+
|
|
173
|
+
Find contacts by property filters, sorts or a text query.
|
|
174
|
+
|
|
175
|
+
`POST /crm/v3/objects/contacts/search` · reads only — safe to replay
|
|
176
|
+
|
|
177
|
+
| Input | Required | What it is |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| `filterGroups` | no | HubSpot's filterGroups: groups are ORed, filters inside a group are ANDed. Example: [{"filters":[{"propertyName":"amount","operator":"GT","value":"1000"}]}]. |
|
|
180
|
+
| `sorts` | no | Example: [{"propertyName":"createdate","direction":"DESCENDING"}]. |
|
|
181
|
+
| `query` | no | Searches the object's default text properties. |
|
|
182
|
+
| `properties` | no | Comma-separated property names to return. |
|
|
183
|
+
| `limit` | no | 1 to 200. |
|
|
184
|
+
| `after` | no | The cursor from the previous page's paging.next.after. |
|
|
185
|
+
|
|
186
|
+
#### `contact_update` — Update contact
|
|
187
|
+
|
|
188
|
+
Change properties on an existing contact.
|
|
189
|
+
|
|
190
|
+
`PATCH /crm/v3/objects/contacts/{contactId}` · idempotent — safe to replay
|
|
191
|
+
|
|
192
|
+
| Input | Required | What it is |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `contactId` | yes | The contact's HubSpot id (a numeric string). |
|
|
195
|
+
| `properties` | yes | HubSpot property name → value for the contact. Values are sent as strings, which is how HubSpot stores every property. |
|
|
196
|
+
|
|
197
|
+
#### `deal_create` — Create deal
|
|
198
|
+
|
|
199
|
+
Create a deal in HubSpot.
|
|
200
|
+
|
|
201
|
+
`POST /crm/v3/objects/deals` · **unsafe to replay** — a retried durable run does it TWICE
|
|
202
|
+
|
|
203
|
+
| Input | Required | What it is |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `properties` | yes | HubSpot property name → value for the new deal. Values are sent as strings, which is how HubSpot stores every property. |
|
|
206
|
+
|
|
207
|
+
#### `deal_get` — Get deal
|
|
208
|
+
|
|
209
|
+
Read one deal, with its associations when asked.
|
|
210
|
+
|
|
211
|
+
`GET /crm/v3/objects/deals/{dealId}` · reads only — safe to replay
|
|
212
|
+
|
|
213
|
+
| Input | Required | What it is |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `dealId` | yes | The deal's HubSpot id (a numeric string). |
|
|
216
|
+
| `properties` | no | Comma-separated property names to return. HubSpot returns a small default set otherwise. |
|
|
217
|
+
| `associations` | no | Comma-separated object types whose associated ids to return. |
|
|
218
|
+
|
|
219
|
+
#### `deal_list` — List deals
|
|
220
|
+
|
|
221
|
+
Page through every deal in the portal.
|
|
222
|
+
|
|
223
|
+
`GET /crm/v3/objects/deals` · reads only — safe to replay
|
|
224
|
+
|
|
225
|
+
| Input | Required | What it is |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `limit` | no | How many records per page, 1 to 100. HubSpot's own default is 10. |
|
|
228
|
+
| `after` | no | The cursor from the previous page's paging.next.after. Leave empty for the first page. |
|
|
229
|
+
| `properties` | no | Comma-separated property names to return. HubSpot returns a small default set otherwise. |
|
|
230
|
+
| `associations` | no | Comma-separated object types whose associated ids to return. |
|
|
231
|
+
|
|
232
|
+
#### `deal_search` — Search deals
|
|
233
|
+
|
|
234
|
+
Find deals by property filters, sorts or a text query.
|
|
235
|
+
|
|
236
|
+
`POST /crm/v3/objects/deals/search` · reads only — safe to replay
|
|
237
|
+
|
|
238
|
+
| Input | Required | What it is |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `filterGroups` | no | HubSpot's filterGroups: groups are ORed, filters inside a group are ANDed. Example: [{"filters":[{"propertyName":"amount","operator":"GT","value":"1000"}]}]. |
|
|
241
|
+
| `sorts` | no | Example: [{"propertyName":"createdate","direction":"DESCENDING"}]. |
|
|
242
|
+
| `query` | no | Searches the object's default text properties. |
|
|
243
|
+
| `properties` | no | Comma-separated property names to return. |
|
|
244
|
+
| `limit` | no | 1 to 200. |
|
|
245
|
+
| `after` | no | The cursor from the previous page's paging.next.after. |
|
|
246
|
+
|
|
247
|
+
#### `deal_update` — Update deal
|
|
248
|
+
|
|
249
|
+
Change properties on an existing deal.
|
|
250
|
+
|
|
251
|
+
`PATCH /crm/v3/objects/deals/{dealId}` · idempotent — safe to replay
|
|
252
|
+
|
|
253
|
+
| Input | Required | What it is |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| `dealId` | yes | The deal's HubSpot id (a numeric string). |
|
|
256
|
+
| `properties` | yes | HubSpot property name → value for the deal. Values are sent as strings, which is how HubSpot stores every property. |
|
|
257
|
+
|
|
258
|
+
#### `owner_list` — List owners
|
|
259
|
+
|
|
260
|
+
Page through the portal's owners: the users records can be assigned to.
|
|
261
|
+
|
|
262
|
+
`GET /crm/v3/owners` · reads only — safe to replay
|
|
263
|
+
|
|
264
|
+
| Input | Required | What it is |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `email` | no | Only the owner with this email. |
|
|
267
|
+
| `limit` | no | How many records per page, 1 to 100. HubSpot's own default is 10. |
|
|
268
|
+
| `after` | no | The cursor from the previous page's paging.next.after. Leave empty for the first page. |
|
|
269
|
+
|
|
270
|
+
#### `pipeline_list` — List deal pipelines
|
|
271
|
+
|
|
272
|
+
Every deal pipeline in the portal, with its stages in order.
|
|
273
|
+
|
|
274
|
+
`GET /crm/v3/pipelines/deals` · reads only — safe to replay
|
|
275
|
+
|
|
276
|
+
Takes no input.
|
|
277
|
+
|
|
278
|
+
#### `property_list` — List property definitions
|
|
279
|
+
|
|
280
|
+
Every property defined on deals, contacts or companies: name, label, type and the options of an enumeration.
|
|
281
|
+
|
|
282
|
+
`GET /crm/v3/properties/{objectType}` · reads only — safe to replay
|
|
283
|
+
|
|
284
|
+
| Input | Required | What it is |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| `objectType` | yes | Object |
|
|
287
|
+
|
|
288
|
+
### Triggers
|
|
289
|
+
|
|
290
|
+
#### `crm_event` — HubSpot CRM event
|
|
291
|
+
|
|
292
|
+
Start when HubSpot reports deals, contacts or companies created, changed, deleted, merged, restored or re-associated.
|
|
293
|
+
|
|
294
|
+
Delivered by webhook, and the signature is verified before anything runs.
|
|
295
|
+
|
|
296
|
+
**You have to set this up with the provider first:**
|
|
297
|
+
|
|
298
|
+
In the HubSpot app's webhook settings, set the target URL to the route your host mounts for this trigger and subscribe to the deal, contact and company events you want. Deliveries are signed with the app's client secret, already on the connection as `clientSecret`. ONE URL serves every portal that installed the app, so route each event by its portalId, and only after the signature has verified: map portalId to the connection created when that portal installed, and drop an unmapped portalId with a 2xx and a log line, creating nothing.
|
|
299
|
+
|
|
83
300
|
## Run it before you have credentials
|
|
84
301
|
|
|
85
302
|
Every operation ships a **faker**, whether or not HubSpot has a sandbox. Set a
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="28" role="img" aria-label="Fancified — built with Fancy UI">
|
|
2
|
+
<title>Fancified · built with Fancy UI</title>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="fcdBg" x1="0" y1="0" x2="1" y2="1">
|
|
5
|
+
<stop offset="0" stop-color="#6d28d9"/>
|
|
6
|
+
<stop offset=".5" stop-color="#9333ea"/>
|
|
7
|
+
<stop offset="1" stop-color="#d946ef"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="fcdGloss" x1="0" y1="0" x2="0" y2="1">
|
|
10
|
+
<stop offset="0" stop-color="#ffffff" stop-opacity=".30"/>
|
|
11
|
+
<stop offset=".55" stop-color="#ffffff" stop-opacity="0"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
<clipPath id="fcdClip"><rect width="128" height="28" rx="7"/></clipPath>
|
|
14
|
+
</defs>
|
|
15
|
+
<g clip-path="url(#fcdClip)">
|
|
16
|
+
<rect width="128" height="28" fill="url(#fcdBg)"/>
|
|
17
|
+
<rect width="34" height="28" fill="#3b0764" fill-opacity=".5"/>
|
|
18
|
+
<rect width="128" height="13" fill="url(#fcdGloss)"/>
|
|
19
|
+
<g fill="#fff">
|
|
20
|
+
<path transform="translate(17 14)" d="M0 -7 C.6 -2.2 2.2 -.6 7 0 C2.2 .6 .6 2.2 0 7 C-.6 2.2 -2.2 .6 -7 0 C-2.2 -.6 -.6 -2.2 0 -7 Z"/>
|
|
21
|
+
<path transform="translate(25.5 7.5) scale(.34)" fill-opacity=".85" d="M0 -7 C.6 -2.2 2.2 -.6 7 0 C2.2 .6 .6 2.2 0 7 C-.6 2.2 -2.2 .6 -7 0 C-2.2 -.6 -.6 -2.2 0 -7 Z"/>
|
|
22
|
+
</g>
|
|
23
|
+
<text x="81" y="18.5" fill="#ffffff" text-anchor="middle" font-family="'Segoe UI',Verdana,'DejaVu Sans',sans-serif" font-size="12" font-weight="700" letter-spacing=".3">Fancified</text>
|
|
24
|
+
</g>
|
|
25
|
+
<rect x=".5" y=".5" width="127" height="27" rx="6.5" fill="none" stroke="#ffffff" stroke-opacity=".22"/>
|
|
26
|
+
</svg>
|