@workbenchcrm/sdk 1.0.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/LICENSE +21 -0
- package/README.md +341 -0
- package/dist/index.d.mts +1533 -0
- package/dist/index.d.ts +1533 -0
- package/dist/index.js +1082 -0
- package/dist/index.mjs +1043 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1043 @@
|
|
|
1
|
+
// src/resources/clients.ts
|
|
2
|
+
var ClientsResource = class {
|
|
3
|
+
client;
|
|
4
|
+
constructor(client) {
|
|
5
|
+
this.client = client;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* List all clients
|
|
9
|
+
*
|
|
10
|
+
* Returns a paginated list of clients for the authenticated business.
|
|
11
|
+
*
|
|
12
|
+
* @param options - List options (pagination, filtering, sorting)
|
|
13
|
+
* @returns Paginated list of clients
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // List all active clients
|
|
18
|
+
* const { data, pagination } = await workbench.clients.list({
|
|
19
|
+
* status: 'active',
|
|
20
|
+
* page: 1,
|
|
21
|
+
* per_page: 20
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* console.log(`Found ${pagination.total} clients`);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
async list(options = {}) {
|
|
28
|
+
return this.client.get("/v1/clients", {
|
|
29
|
+
page: options.page,
|
|
30
|
+
per_page: options.per_page,
|
|
31
|
+
search: options.search,
|
|
32
|
+
sort: options.sort,
|
|
33
|
+
order: options.order,
|
|
34
|
+
status: options.status
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get a client by ID
|
|
39
|
+
*
|
|
40
|
+
* @param id - Client UUID
|
|
41
|
+
* @returns Client details
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const { data: client } = await workbench.clients.get('client-uuid');
|
|
46
|
+
* console.log(`Client: ${client.first_name} ${client.last_name}`);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
async get(id) {
|
|
50
|
+
return this.client.get(`/v1/clients/${id}`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a new client
|
|
54
|
+
*
|
|
55
|
+
* @param data - Client data
|
|
56
|
+
* @returns Created client
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const { data: client } = await workbench.clients.create({
|
|
61
|
+
* first_name: 'John',
|
|
62
|
+
* last_name: 'Doe',
|
|
63
|
+
* email: 'john@example.com',
|
|
64
|
+
* phone: '+1-555-123-4567',
|
|
65
|
+
* company: 'Acme Corp',
|
|
66
|
+
* status: 'active',
|
|
67
|
+
* source: 'referral',
|
|
68
|
+
* tags: ['vip', 'commercial']
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
async create(data) {
|
|
73
|
+
return this.client.post("/v1/clients", data);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Update a client
|
|
77
|
+
*
|
|
78
|
+
* @param id - Client UUID
|
|
79
|
+
* @param data - Fields to update
|
|
80
|
+
* @returns Updated client
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const { data: client } = await workbench.clients.update('client-uuid', {
|
|
85
|
+
* status: 'inactive',
|
|
86
|
+
* notes: 'Client requested to pause services'
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
async update(id, data) {
|
|
91
|
+
return this.client.put(`/v1/clients/${id}`, data);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Delete a client
|
|
95
|
+
*
|
|
96
|
+
* Permanently deletes a client and all associated data.
|
|
97
|
+
* This action cannot be undone.
|
|
98
|
+
*
|
|
99
|
+
* @param id - Client UUID
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* await workbench.clients.delete('client-uuid');
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
async delete(id) {
|
|
107
|
+
await this.client.delete(`/v1/clients/${id}`);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/resources/invoices.ts
|
|
112
|
+
var InvoicesResource = class {
|
|
113
|
+
client;
|
|
114
|
+
constructor(client) {
|
|
115
|
+
this.client = client;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* List all invoices
|
|
119
|
+
*
|
|
120
|
+
* Returns a paginated list of invoices for the authenticated business.
|
|
121
|
+
*
|
|
122
|
+
* @param options - List options (pagination, filtering, sorting)
|
|
123
|
+
* @returns Paginated list of invoices
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* // List unpaid invoices
|
|
128
|
+
* const { data, pagination } = await workbench.invoices.list({
|
|
129
|
+
* status: 'sent',
|
|
130
|
+
* per_page: 50
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
async list(options = {}) {
|
|
135
|
+
return this.client.get("/v1/invoices", {
|
|
136
|
+
page: options.page,
|
|
137
|
+
per_page: options.per_page,
|
|
138
|
+
search: options.search,
|
|
139
|
+
sort: options.sort,
|
|
140
|
+
order: options.order,
|
|
141
|
+
status: options.status,
|
|
142
|
+
client_id: options.client_id
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get an invoice by ID
|
|
147
|
+
*
|
|
148
|
+
* @param id - Invoice UUID
|
|
149
|
+
* @returns Invoice details with line items
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* const { data: invoice } = await workbench.invoices.get('invoice-uuid');
|
|
154
|
+
* console.log(`Invoice ${invoice.invoice_number}: $${invoice.total}`);
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
async get(id) {
|
|
158
|
+
return this.client.get(`/v1/invoices/${id}`);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Create a new invoice
|
|
162
|
+
*
|
|
163
|
+
* Creates an invoice with line items. The invoice number is
|
|
164
|
+
* automatically generated by Workbench.
|
|
165
|
+
*
|
|
166
|
+
* @param data - Invoice data including line items
|
|
167
|
+
* @returns Created invoice
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const { data: invoice } = await workbench.invoices.create({
|
|
172
|
+
* client_id: 'client-uuid',
|
|
173
|
+
* due_date: '2024-02-15',
|
|
174
|
+
* items: [
|
|
175
|
+
* { description: 'Web Development', quantity: 10, unit_price: 100 }
|
|
176
|
+
* ],
|
|
177
|
+
* tax_rate: 8.5,
|
|
178
|
+
* notes: 'Payment due within 30 days'
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
async create(data) {
|
|
183
|
+
return this.client.post("/v1/invoices", data);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Update an invoice
|
|
187
|
+
*
|
|
188
|
+
* If items are provided, they will replace all existing line items.
|
|
189
|
+
*
|
|
190
|
+
* @param id - Invoice UUID
|
|
191
|
+
* @param data - Fields to update
|
|
192
|
+
* @returns Updated invoice
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const { data: invoice } = await workbench.invoices.update('invoice-uuid', {
|
|
197
|
+
* status: 'paid',
|
|
198
|
+
* notes: 'Paid via bank transfer on 2024-01-15'
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
async update(id, data) {
|
|
203
|
+
return this.client.put(`/v1/invoices/${id}`, data);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Delete an invoice
|
|
207
|
+
*
|
|
208
|
+
* Permanently deletes an invoice and all associated line items.
|
|
209
|
+
* This action cannot be undone.
|
|
210
|
+
*
|
|
211
|
+
* @param id - Invoice UUID
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* await workbench.invoices.delete('invoice-uuid');
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
async delete(id) {
|
|
219
|
+
await this.client.delete(`/v1/invoices/${id}`);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Send an invoice via email
|
|
223
|
+
*
|
|
224
|
+
* Sends the invoice to the client's email address. The invoice
|
|
225
|
+
* status will be updated to 'sent' if currently 'draft'.
|
|
226
|
+
*
|
|
227
|
+
* @param id - Invoice UUID
|
|
228
|
+
* @returns Success response
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* await workbench.invoices.send('invoice-uuid');
|
|
233
|
+
* console.log('Invoice sent successfully');
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
async send(id) {
|
|
237
|
+
return this.client.post(`/v1/invoices/${id}/send`);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/resources/quotes.ts
|
|
242
|
+
var QuotesResource = class {
|
|
243
|
+
client;
|
|
244
|
+
constructor(client) {
|
|
245
|
+
this.client = client;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* List all quotes
|
|
249
|
+
*
|
|
250
|
+
* Returns a paginated list of quotes for the authenticated business.
|
|
251
|
+
*
|
|
252
|
+
* @param options - List options (pagination, filtering, sorting)
|
|
253
|
+
* @returns Paginated list of quotes
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* // List pending quotes
|
|
258
|
+
* const { data, pagination } = await workbench.quotes.list({
|
|
259
|
+
* status: 'sent',
|
|
260
|
+
* per_page: 20
|
|
261
|
+
* });
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
async list(options = {}) {
|
|
265
|
+
return this.client.get("/v1/quotes", {
|
|
266
|
+
page: options.page,
|
|
267
|
+
per_page: options.per_page,
|
|
268
|
+
search: options.search,
|
|
269
|
+
sort: options.sort,
|
|
270
|
+
order: options.order,
|
|
271
|
+
status: options.status,
|
|
272
|
+
client_id: options.client_id
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get a quote by ID
|
|
277
|
+
*
|
|
278
|
+
* @param id - Quote UUID
|
|
279
|
+
* @returns Quote details with line items
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* const { data: quote } = await workbench.quotes.get('quote-uuid');
|
|
284
|
+
* console.log(`Quote ${quote.quote_number}: $${quote.total}`);
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
async get(id) {
|
|
288
|
+
return this.client.get(`/v1/quotes/${id}`);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Create a new quote
|
|
292
|
+
*
|
|
293
|
+
* Creates a quote with line items. The quote number is
|
|
294
|
+
* automatically generated by Workbench.
|
|
295
|
+
*
|
|
296
|
+
* @param data - Quote data including line items
|
|
297
|
+
* @returns Created quote
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* const { data: quote } = await workbench.quotes.create({
|
|
302
|
+
* client_id: 'client-uuid',
|
|
303
|
+
* valid_until: '2024-03-01',
|
|
304
|
+
* items: [
|
|
305
|
+
* { description: 'Plumbing Service', quantity: 4, unit_price: 75 }
|
|
306
|
+
* ],
|
|
307
|
+
* notes: 'Quote valid for 30 days',
|
|
308
|
+
* terms: 'Payment due upon completion'
|
|
309
|
+
* });
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
async create(data) {
|
|
313
|
+
return this.client.post("/v1/quotes", data);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Update a quote
|
|
317
|
+
*
|
|
318
|
+
* If items are provided, they will replace all existing line items.
|
|
319
|
+
*
|
|
320
|
+
* @param id - Quote UUID
|
|
321
|
+
* @param data - Fields to update
|
|
322
|
+
* @returns Updated quote
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```typescript
|
|
326
|
+
* const { data: quote } = await workbench.quotes.update('quote-uuid', {
|
|
327
|
+
* status: 'approved',
|
|
328
|
+
* notes: 'Client approved via email on 2024-01-15'
|
|
329
|
+
* });
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
async update(id, data) {
|
|
333
|
+
return this.client.put(`/v1/quotes/${id}`, data);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Delete a quote
|
|
337
|
+
*
|
|
338
|
+
* Permanently deletes a quote and all associated line items.
|
|
339
|
+
* This action cannot be undone.
|
|
340
|
+
*
|
|
341
|
+
* @param id - Quote UUID
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```typescript
|
|
345
|
+
* await workbench.quotes.delete('quote-uuid');
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
async delete(id) {
|
|
349
|
+
await this.client.delete(`/v1/quotes/${id}`);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Send a quote via email
|
|
353
|
+
*
|
|
354
|
+
* Sends the quote to the client's email address. The quote
|
|
355
|
+
* status will be updated to 'sent' if currently 'draft'.
|
|
356
|
+
*
|
|
357
|
+
* @param id - Quote UUID
|
|
358
|
+
* @returns Success response
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```typescript
|
|
362
|
+
* await workbench.quotes.send('quote-uuid');
|
|
363
|
+
* console.log('Quote sent successfully');
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
async send(id) {
|
|
367
|
+
return this.client.post(`/v1/quotes/${id}/send`);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/resources/jobs.ts
|
|
372
|
+
var JobsResource = class {
|
|
373
|
+
client;
|
|
374
|
+
constructor(client) {
|
|
375
|
+
this.client = client;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* List all jobs
|
|
379
|
+
*
|
|
380
|
+
* Returns a paginated list of jobs for the authenticated business.
|
|
381
|
+
*
|
|
382
|
+
* @param options - List options (pagination, filtering, sorting)
|
|
383
|
+
* @returns Paginated list of jobs
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* // List scheduled jobs
|
|
388
|
+
* const { data, pagination } = await workbench.jobs.list({
|
|
389
|
+
* status: 'scheduled',
|
|
390
|
+
* priority: 'high',
|
|
391
|
+
* per_page: 20
|
|
392
|
+
* });
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
async list(options = {}) {
|
|
396
|
+
return this.client.get("/v1/jobs", {
|
|
397
|
+
page: options.page,
|
|
398
|
+
per_page: options.per_page,
|
|
399
|
+
search: options.search,
|
|
400
|
+
sort: options.sort,
|
|
401
|
+
order: options.order,
|
|
402
|
+
status: options.status,
|
|
403
|
+
priority: options.priority,
|
|
404
|
+
client_id: options.client_id
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get a job by ID
|
|
409
|
+
*
|
|
410
|
+
* @param id - Job UUID
|
|
411
|
+
* @returns Job details
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```typescript
|
|
415
|
+
* const { data: job } = await workbench.jobs.get('job-uuid');
|
|
416
|
+
* console.log(`Job: ${job.title} (${job.status})`);
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
async get(id) {
|
|
420
|
+
return this.client.get(`/v1/jobs/${id}`);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Create a new job
|
|
424
|
+
*
|
|
425
|
+
* @param data - Job data
|
|
426
|
+
* @returns Created job
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* const { data: job } = await workbench.jobs.create({
|
|
431
|
+
* client_id: 'client-uuid',
|
|
432
|
+
* title: 'HVAC Maintenance',
|
|
433
|
+
* description: 'Annual AC maintenance and filter replacement',
|
|
434
|
+
* priority: 'medium',
|
|
435
|
+
* scheduled_start: '2024-01-25T10:00:00Z',
|
|
436
|
+
* estimated_duration: 120, // minutes
|
|
437
|
+
* notes: 'Customer prefers morning appointments'
|
|
438
|
+
* });
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
async create(data) {
|
|
442
|
+
return this.client.post("/v1/jobs", data);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Update a job
|
|
446
|
+
*
|
|
447
|
+
* @param id - Job UUID
|
|
448
|
+
* @param data - Fields to update
|
|
449
|
+
* @returns Updated job
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```typescript
|
|
453
|
+
* // Mark job as started
|
|
454
|
+
* const { data: job } = await workbench.jobs.update('job-uuid', {
|
|
455
|
+
* status: 'in_progress',
|
|
456
|
+
* actual_start: new Date().toISOString()
|
|
457
|
+
* });
|
|
458
|
+
*
|
|
459
|
+
* // Mark job as completed
|
|
460
|
+
* await workbench.jobs.update('job-uuid', {
|
|
461
|
+
* status: 'completed',
|
|
462
|
+
* actual_end: new Date().toISOString()
|
|
463
|
+
* });
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
async update(id, data) {
|
|
467
|
+
return this.client.put(`/v1/jobs/${id}`, data);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Delete a job
|
|
471
|
+
*
|
|
472
|
+
* Permanently deletes a job. This action cannot be undone.
|
|
473
|
+
*
|
|
474
|
+
* @param id - Job UUID
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```typescript
|
|
478
|
+
* await workbench.jobs.delete('job-uuid');
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
async delete(id) {
|
|
482
|
+
await this.client.delete(`/v1/jobs/${id}`);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// src/resources/service-requests.ts
|
|
487
|
+
var ServiceRequestsResource = class {
|
|
488
|
+
client;
|
|
489
|
+
constructor(client) {
|
|
490
|
+
this.client = client;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* List all service requests
|
|
494
|
+
*
|
|
495
|
+
* Returns a paginated list of service requests for the authenticated business.
|
|
496
|
+
*
|
|
497
|
+
* @param options - List options (pagination, filtering, sorting)
|
|
498
|
+
* @returns Paginated list of service requests
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* // List new requests
|
|
503
|
+
* const { data, pagination } = await workbench.serviceRequests.list({
|
|
504
|
+
* status: 'new',
|
|
505
|
+
* priority: 'urgent',
|
|
506
|
+
* per_page: 50
|
|
507
|
+
* });
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
async list(options = {}) {
|
|
511
|
+
return this.client.get("/v1/service-requests", {
|
|
512
|
+
page: options.page,
|
|
513
|
+
per_page: options.per_page,
|
|
514
|
+
search: options.search,
|
|
515
|
+
sort: options.sort,
|
|
516
|
+
order: options.order,
|
|
517
|
+
status: options.status,
|
|
518
|
+
priority: options.priority,
|
|
519
|
+
client_id: options.client_id
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Get a service request by ID
|
|
524
|
+
*
|
|
525
|
+
* @param id - Service request UUID
|
|
526
|
+
* @returns Service request details
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```typescript
|
|
530
|
+
* const { data: request } = await workbench.serviceRequests.get('request-uuid');
|
|
531
|
+
* console.log(`Request: ${request.title} (${request.status})`);
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
async get(id) {
|
|
535
|
+
return this.client.get(`/v1/service-requests/${id}`);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Create a new service request
|
|
539
|
+
*
|
|
540
|
+
* @param data - Service request data
|
|
541
|
+
* @returns Created service request
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* ```typescript
|
|
545
|
+
* const { data: request } = await workbench.serviceRequests.create({
|
|
546
|
+
* title: 'AC Not Cooling',
|
|
547
|
+
* description: 'Air conditioner is running but not producing cold air',
|
|
548
|
+
* contact_name: 'John Doe',
|
|
549
|
+
* contact_email: 'john@example.com',
|
|
550
|
+
* contact_phone: '+1-555-123-4567',
|
|
551
|
+
* address: '456 Oak Ave, Anytown, USA',
|
|
552
|
+
* priority: 'urgent',
|
|
553
|
+
* requested_date: '2024-01-20',
|
|
554
|
+
* preferred_time: 'Morning (8am-12pm)',
|
|
555
|
+
* source: 'website'
|
|
556
|
+
* });
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
async create(data) {
|
|
560
|
+
return this.client.post("/v1/service-requests", data);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Update a service request
|
|
564
|
+
*
|
|
565
|
+
* @param id - Service request UUID
|
|
566
|
+
* @param data - Fields to update
|
|
567
|
+
* @returns Updated service request
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```typescript
|
|
571
|
+
* // Assign to client and schedule
|
|
572
|
+
* const { data: request } = await workbench.serviceRequests.update('request-uuid', {
|
|
573
|
+
* client_id: 'client-uuid',
|
|
574
|
+
* status: 'scheduled',
|
|
575
|
+
* notes: 'Scheduled for Monday morning'
|
|
576
|
+
* });
|
|
577
|
+
* ```
|
|
578
|
+
*/
|
|
579
|
+
async update(id, data) {
|
|
580
|
+
return this.client.put(`/v1/service-requests/${id}`, data);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Delete a service request
|
|
584
|
+
*
|
|
585
|
+
* Permanently deletes a service request. This action cannot be undone.
|
|
586
|
+
*
|
|
587
|
+
* @param id - Service request UUID
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```typescript
|
|
591
|
+
* await workbench.serviceRequests.delete('request-uuid');
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
async delete(id) {
|
|
595
|
+
await this.client.delete(`/v1/service-requests/${id}`);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/resources/webhooks.ts
|
|
600
|
+
var WebhooksResource = class {
|
|
601
|
+
client;
|
|
602
|
+
constructor(client) {
|
|
603
|
+
this.client = client;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* List all webhooks
|
|
607
|
+
*
|
|
608
|
+
* Returns a paginated list of webhook subscriptions for the authenticated business.
|
|
609
|
+
*
|
|
610
|
+
* @param options - List options (pagination)
|
|
611
|
+
* @returns Paginated list of webhooks
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```typescript
|
|
615
|
+
* const { data: webhooks } = await workbench.webhooks.list();
|
|
616
|
+
* webhooks.forEach(webhook => {
|
|
617
|
+
* console.log(`${webhook.name}: ${webhook.events.join(', ')}`);
|
|
618
|
+
* });
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
async list(options = {}) {
|
|
622
|
+
return this.client.get("/v1/webhooks", {
|
|
623
|
+
page: options.page,
|
|
624
|
+
per_page: options.per_page
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Get a webhook by ID
|
|
629
|
+
*
|
|
630
|
+
* @param id - Webhook UUID
|
|
631
|
+
* @returns Webhook details
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```typescript
|
|
635
|
+
* const { data: webhook } = await workbench.webhooks.get('webhook-uuid');
|
|
636
|
+
* console.log(`Webhook: ${webhook.name}`);
|
|
637
|
+
* console.log(`Events: ${webhook.events.join(', ')}`);
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
async get(id) {
|
|
641
|
+
return this.client.get(`/v1/webhooks/${id}`);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Create a new webhook
|
|
645
|
+
*
|
|
646
|
+
* Creates a webhook subscription. The webhook secret is returned
|
|
647
|
+
* in the response - store it securely to verify webhook signatures.
|
|
648
|
+
*
|
|
649
|
+
* @param data - Webhook data
|
|
650
|
+
* @returns Created webhook (includes secret)
|
|
651
|
+
*
|
|
652
|
+
* @example
|
|
653
|
+
* ```typescript
|
|
654
|
+
* const { data: webhook } = await workbench.webhooks.create({
|
|
655
|
+
* name: 'All Events',
|
|
656
|
+
* url: 'https://example.com/webhooks',
|
|
657
|
+
* events: [
|
|
658
|
+
* 'client.created',
|
|
659
|
+
* 'client.updated',
|
|
660
|
+
* 'invoice.created',
|
|
661
|
+
* 'invoice.paid',
|
|
662
|
+
* 'quote.accepted',
|
|
663
|
+
* 'job.completed'
|
|
664
|
+
* ]
|
|
665
|
+
* });
|
|
666
|
+
*
|
|
667
|
+
* // IMPORTANT: Store the secret securely!
|
|
668
|
+
* console.log('Store this secret:', webhook.secret);
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
async create(data) {
|
|
672
|
+
return this.client.post("/v1/webhooks", data);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Update a webhook
|
|
676
|
+
*
|
|
677
|
+
* @param id - Webhook UUID
|
|
678
|
+
* @param data - Fields to update
|
|
679
|
+
* @returns Updated webhook
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```typescript
|
|
683
|
+
* // Add more events to the webhook
|
|
684
|
+
* const { data: webhook } = await workbench.webhooks.update('webhook-uuid', {
|
|
685
|
+
* events: ['invoice.created', 'invoice.paid', 'invoice.overdue']
|
|
686
|
+
* });
|
|
687
|
+
*
|
|
688
|
+
* // Disable a webhook
|
|
689
|
+
* await workbench.webhooks.update('webhook-uuid', { is_active: false });
|
|
690
|
+
* ```
|
|
691
|
+
*/
|
|
692
|
+
async update(id, data) {
|
|
693
|
+
return this.client.put(`/v1/webhooks/${id}`, data);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Delete a webhook
|
|
697
|
+
*
|
|
698
|
+
* Permanently deletes a webhook subscription. No more events
|
|
699
|
+
* will be sent to this endpoint.
|
|
700
|
+
*
|
|
701
|
+
* @param id - Webhook UUID
|
|
702
|
+
*
|
|
703
|
+
* @example
|
|
704
|
+
* ```typescript
|
|
705
|
+
* await workbench.webhooks.delete('webhook-uuid');
|
|
706
|
+
* ```
|
|
707
|
+
*/
|
|
708
|
+
async delete(id) {
|
|
709
|
+
await this.client.delete(`/v1/webhooks/${id}`);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* List webhook deliveries
|
|
713
|
+
*
|
|
714
|
+
* Returns recent delivery attempts for a webhook. Useful for
|
|
715
|
+
* debugging and monitoring webhook health.
|
|
716
|
+
*
|
|
717
|
+
* @param webhookId - Webhook UUID
|
|
718
|
+
* @param options - List options (pagination, filtering)
|
|
719
|
+
* @returns Paginated list of delivery attempts
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```typescript
|
|
723
|
+
* const { data: deliveries } = await workbench.webhooks.listDeliveries('webhook-uuid', {
|
|
724
|
+
* per_page: 50
|
|
725
|
+
* });
|
|
726
|
+
*
|
|
727
|
+
* deliveries.forEach(delivery => {
|
|
728
|
+
* const status = delivery.delivered_at ? 'delivered' : 'pending';
|
|
729
|
+
* console.log(`${delivery.event_type}: ${status}`);
|
|
730
|
+
* });
|
|
731
|
+
* ```
|
|
732
|
+
*/
|
|
733
|
+
async listDeliveries(webhookId, options = {}) {
|
|
734
|
+
return this.client.get(`/v1/webhooks/${webhookId}/deliveries`, {
|
|
735
|
+
page: options.page,
|
|
736
|
+
per_page: options.per_page,
|
|
737
|
+
event_type: options.event_type
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Send a test webhook
|
|
742
|
+
*
|
|
743
|
+
* Sends a test event to the webhook endpoint. Useful for
|
|
744
|
+
* verifying your webhook handler is working correctly.
|
|
745
|
+
*
|
|
746
|
+
* @param id - Webhook UUID
|
|
747
|
+
* @returns Test delivery result
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```typescript
|
|
751
|
+
* const { data: result } = await workbench.webhooks.test('webhook-uuid');
|
|
752
|
+
* console.log('Test delivery:', result);
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
755
|
+
async test(id) {
|
|
756
|
+
return this.client.post(`/v1/webhooks/${id}/test`);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// src/client.ts
|
|
761
|
+
var DEFAULT_BASE_URL = "https://api.tryworkbench.app";
|
|
762
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
763
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
764
|
+
var WorkbenchError = class extends Error {
|
|
765
|
+
/** HTTP status code */
|
|
766
|
+
status;
|
|
767
|
+
/** Error code from the API */
|
|
768
|
+
code;
|
|
769
|
+
/** Additional error details */
|
|
770
|
+
details;
|
|
771
|
+
/** Request ID for debugging */
|
|
772
|
+
requestId;
|
|
773
|
+
constructor(message, status, code, details, requestId) {
|
|
774
|
+
super(message);
|
|
775
|
+
this.name = "WorkbenchError";
|
|
776
|
+
this.status = status;
|
|
777
|
+
this.code = code;
|
|
778
|
+
this.details = details;
|
|
779
|
+
this.requestId = requestId;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
var WorkbenchClient = class {
|
|
783
|
+
baseUrl;
|
|
784
|
+
timeout;
|
|
785
|
+
maxRetries;
|
|
786
|
+
authHeader;
|
|
787
|
+
/** Clients resource */
|
|
788
|
+
clients;
|
|
789
|
+
/** Invoices resource */
|
|
790
|
+
invoices;
|
|
791
|
+
/** Quotes resource */
|
|
792
|
+
quotes;
|
|
793
|
+
/** Jobs resource */
|
|
794
|
+
jobs;
|
|
795
|
+
/** Service requests resource */
|
|
796
|
+
serviceRequests;
|
|
797
|
+
/** Webhooks resource */
|
|
798
|
+
webhooks;
|
|
799
|
+
/**
|
|
800
|
+
* Create a new Workbench client
|
|
801
|
+
*
|
|
802
|
+
* @param config - Client configuration
|
|
803
|
+
* @throws Error if neither apiKey nor accessToken is provided
|
|
804
|
+
*/
|
|
805
|
+
constructor(config) {
|
|
806
|
+
if (!config.apiKey && !config.accessToken) {
|
|
807
|
+
throw new Error("Either apiKey or accessToken must be provided");
|
|
808
|
+
}
|
|
809
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
810
|
+
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
811
|
+
this.maxRetries = config.maxRetries || DEFAULT_MAX_RETRIES;
|
|
812
|
+
const token = config.accessToken || config.apiKey;
|
|
813
|
+
this.authHeader = `Bearer ${token}`;
|
|
814
|
+
this.clients = new ClientsResource(this);
|
|
815
|
+
this.invoices = new InvoicesResource(this);
|
|
816
|
+
this.quotes = new QuotesResource(this);
|
|
817
|
+
this.jobs = new JobsResource(this);
|
|
818
|
+
this.serviceRequests = new ServiceRequestsResource(this);
|
|
819
|
+
this.webhooks = new WebhooksResource(this);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Build URL with query parameters
|
|
823
|
+
*/
|
|
824
|
+
buildUrl(path, query) {
|
|
825
|
+
const url = new URL(path, this.baseUrl);
|
|
826
|
+
if (query) {
|
|
827
|
+
for (const [key, value] of Object.entries(query)) {
|
|
828
|
+
if (value !== void 0) {
|
|
829
|
+
url.searchParams.append(key, String(value));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return url.toString();
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Sleep for a specified duration
|
|
837
|
+
*/
|
|
838
|
+
sleep(ms) {
|
|
839
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Calculate exponential backoff delay
|
|
843
|
+
*/
|
|
844
|
+
getRetryDelay(attempt) {
|
|
845
|
+
return Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Determine if an error is retryable
|
|
849
|
+
*/
|
|
850
|
+
isRetryable(status) {
|
|
851
|
+
return status === 429 || status >= 500 && status < 600;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Make an API request
|
|
855
|
+
*
|
|
856
|
+
* @param options - Request options
|
|
857
|
+
* @returns API response
|
|
858
|
+
* @throws WorkbenchError if the request fails
|
|
859
|
+
*/
|
|
860
|
+
async request(options) {
|
|
861
|
+
const { method, path, query, body, headers } = options;
|
|
862
|
+
const url = this.buildUrl(path, query);
|
|
863
|
+
const requestHeaders = {
|
|
864
|
+
"Authorization": this.authHeader,
|
|
865
|
+
"Content-Type": "application/json",
|
|
866
|
+
"Accept": "application/json",
|
|
867
|
+
...headers
|
|
868
|
+
};
|
|
869
|
+
let lastError = null;
|
|
870
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
871
|
+
try {
|
|
872
|
+
const controller = new AbortController();
|
|
873
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
874
|
+
const response = await fetch(url, {
|
|
875
|
+
method,
|
|
876
|
+
headers: requestHeaders,
|
|
877
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
878
|
+
signal: controller.signal
|
|
879
|
+
});
|
|
880
|
+
clearTimeout(timeoutId);
|
|
881
|
+
const responseText = await response.text();
|
|
882
|
+
let responseData;
|
|
883
|
+
try {
|
|
884
|
+
responseData = responseText ? JSON.parse(responseText) : {};
|
|
885
|
+
} catch {
|
|
886
|
+
throw new WorkbenchError(
|
|
887
|
+
"Invalid JSON response from API",
|
|
888
|
+
response.status,
|
|
889
|
+
"INVALID_RESPONSE"
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
if (!response.ok) {
|
|
893
|
+
const errorResponse = responseData;
|
|
894
|
+
if (this.isRetryable(response.status) && attempt < this.maxRetries) {
|
|
895
|
+
const delay = this.getRetryDelay(attempt);
|
|
896
|
+
await this.sleep(delay);
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
throw new WorkbenchError(
|
|
900
|
+
errorResponse.error?.message || "Unknown error",
|
|
901
|
+
response.status,
|
|
902
|
+
errorResponse.error?.code || "UNKNOWN_ERROR",
|
|
903
|
+
errorResponse.error?.details,
|
|
904
|
+
errorResponse.meta?.request_id
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
return responseData;
|
|
908
|
+
} catch (error) {
|
|
909
|
+
if (error instanceof WorkbenchError) {
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
913
|
+
lastError = new WorkbenchError(
|
|
914
|
+
"Request timeout",
|
|
915
|
+
0,
|
|
916
|
+
"TIMEOUT"
|
|
917
|
+
);
|
|
918
|
+
} else {
|
|
919
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
920
|
+
}
|
|
921
|
+
if (attempt < this.maxRetries) {
|
|
922
|
+
const delay = this.getRetryDelay(attempt);
|
|
923
|
+
await this.sleep(delay);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
throw lastError || new WorkbenchError("Request failed", 0, "UNKNOWN_ERROR");
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Make a GET request
|
|
932
|
+
*/
|
|
933
|
+
async get(path, query) {
|
|
934
|
+
return this.request({ method: "GET", path, query });
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Make a POST request
|
|
938
|
+
*/
|
|
939
|
+
async post(path, body) {
|
|
940
|
+
return this.request({ method: "POST", path, body });
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Make a PUT request
|
|
944
|
+
*/
|
|
945
|
+
async put(path, body) {
|
|
946
|
+
return this.request({ method: "PUT", path, body });
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Make a DELETE request
|
|
950
|
+
*/
|
|
951
|
+
async delete(path) {
|
|
952
|
+
return this.request({ method: "DELETE", path });
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// src/utils/webhook-verify.ts
|
|
957
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
958
|
+
var WebhookVerificationError = class extends Error {
|
|
959
|
+
constructor(message) {
|
|
960
|
+
super(message);
|
|
961
|
+
this.name = "WebhookVerificationError";
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
function parseSignatureHeader(header) {
|
|
965
|
+
if (!header || typeof header !== "string") {
|
|
966
|
+
throw new WebhookVerificationError("Missing or invalid signature header");
|
|
967
|
+
}
|
|
968
|
+
const parts = header.split(",");
|
|
969
|
+
let timestamp;
|
|
970
|
+
let signature;
|
|
971
|
+
for (const part of parts) {
|
|
972
|
+
const [key, value] = part.split("=");
|
|
973
|
+
if (key === "t") {
|
|
974
|
+
timestamp = parseInt(value, 10);
|
|
975
|
+
if (isNaN(timestamp)) {
|
|
976
|
+
throw new WebhookVerificationError("Invalid timestamp in signature header");
|
|
977
|
+
}
|
|
978
|
+
} else if (key === "v1") {
|
|
979
|
+
signature = value;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (timestamp === void 0 || !signature) {
|
|
983
|
+
throw new WebhookVerificationError("Invalid signature header format");
|
|
984
|
+
}
|
|
985
|
+
return { timestamp, signature };
|
|
986
|
+
}
|
|
987
|
+
function computeSignature(payload, secret, timestamp) {
|
|
988
|
+
const payloadString = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
989
|
+
const signedPayload = `${timestamp}.${payloadString}`;
|
|
990
|
+
return createHmac("sha256", secret).update(signedPayload).digest("hex");
|
|
991
|
+
}
|
|
992
|
+
function verifyWebhookSignature(payload, signature, secret, options = {}) {
|
|
993
|
+
const { tolerance = 300 } = options;
|
|
994
|
+
const { timestamp, signature: providedSignature } = parseSignatureHeader(signature);
|
|
995
|
+
if (tolerance > 0) {
|
|
996
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
997
|
+
const age = now - timestamp;
|
|
998
|
+
if (age > tolerance) {
|
|
999
|
+
throw new WebhookVerificationError(
|
|
1000
|
+
`Webhook timestamp is too old (${age} seconds). Maximum allowed age is ${tolerance} seconds.`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
if (age < -tolerance) {
|
|
1004
|
+
throw new WebhookVerificationError(
|
|
1005
|
+
"Webhook timestamp is in the future. Check your server clock."
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const expectedSignature = computeSignature(payload, secret, timestamp);
|
|
1010
|
+
const expectedBuffer = Buffer.from(expectedSignature, "utf8");
|
|
1011
|
+
const providedBuffer = Buffer.from(providedSignature, "utf8");
|
|
1012
|
+
if (expectedBuffer.length !== providedBuffer.length) {
|
|
1013
|
+
throw new WebhookVerificationError("Invalid webhook signature");
|
|
1014
|
+
}
|
|
1015
|
+
if (!timingSafeEqual(expectedBuffer, providedBuffer)) {
|
|
1016
|
+
throw new WebhookVerificationError("Invalid webhook signature");
|
|
1017
|
+
}
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
function constructWebhookEvent(payload, signature, secret, options = {}) {
|
|
1021
|
+
verifyWebhookSignature(payload, signature, secret, options);
|
|
1022
|
+
const payloadString = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
1023
|
+
try {
|
|
1024
|
+
return JSON.parse(payloadString);
|
|
1025
|
+
} catch {
|
|
1026
|
+
throw new WebhookVerificationError("Invalid webhook payload: not valid JSON");
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
export {
|
|
1030
|
+
ClientsResource,
|
|
1031
|
+
InvoicesResource,
|
|
1032
|
+
JobsResource,
|
|
1033
|
+
QuotesResource,
|
|
1034
|
+
ServiceRequestsResource,
|
|
1035
|
+
WebhookVerificationError,
|
|
1036
|
+
WebhooksResource,
|
|
1037
|
+
WorkbenchClient,
|
|
1038
|
+
WorkbenchError,
|
|
1039
|
+
computeSignature,
|
|
1040
|
+
constructWebhookEvent,
|
|
1041
|
+
parseSignatureHeader,
|
|
1042
|
+
verifyWebhookSignature
|
|
1043
|
+
};
|