@agility/management-sdk 0.1.38 → 0.1.40
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/.github/workflows/npm-publish.yml +61 -0
- package/README.md +396 -396
- package/dist/apiMethods/webhookMethods.d.ts +11 -2
- package/dist/apiMethods/webhookMethods.js +58 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/models/exception.d.ts +4 -0
- package/dist/models/exception.js +17 -1
- package/dist/models/pagedResult.d.ts +4 -0
- package/dist/models/webhook.d.ts +17 -0
- package/dist/models/webhookHistory.d.ts +32 -0
- package/dist/models/webhookHistory.js +9 -0
- package/docs/asset-methods.md +381 -381
- package/docs/auth-component.md +360 -360
- package/docs/auth.md +593 -593
- package/docs/batch-methods.md +243 -243
- package/docs/cicd.md +606 -606
- package/docs/container-methods.md +267 -267
- package/docs/content-methods.md +491 -491
- package/docs/instance-methods.md +185 -185
- package/docs/instance-user-methods.md +230 -230
- package/docs/model-methods.md +433 -433
- package/docs/multi-instance-operations.md +191 -191
- package/docs/page-methods.md +737 -737
- package/docs/server-user-methods.md +279 -279
- package/docs/webhook-methods.md +351 -452
- package/license.txt +20 -20
- package/package.json +42 -41
- package/tsconfig.json +14 -14
package/docs/webhook-methods.md
CHANGED
|
@@ -1,452 +1,351 @@
|
|
|
1
|
-
# Agility CMS & Management API TypeScript SDK
|
|
2
|
-
|
|
3
|
-
## WebhookMethods
|
|
4
|
-
|
|
5
|
-
This class provides
|
|
6
|
-
|
|
7
|
-
**Important Notes:**
|
|
8
|
-
- Webhooks
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
### Function List
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [saveWebhook](#savewebhook) - Creates or updates a webhook
|
|
19
|
-
- [deleteWebhook](#deletewebhook) - Deletes a webhook by ID
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
###
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- `
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
|
173
|
-
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
**
|
|
316
|
-
-
|
|
317
|
-
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
console.log(`About to delete webhook: ${webhook.name}`);
|
|
353
|
-
console.log(`URL: ${webhook.url}`);
|
|
354
|
-
console.log(`Events: ${webhook.events.join(', ')}`);
|
|
355
|
-
|
|
356
|
-
// Check if webhook is active
|
|
357
|
-
if (webhook.isActive) {
|
|
358
|
-
console.log('Warning: Deleting active webhook');
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Perform deletion
|
|
362
|
-
const result = await apiClient.webhookMethods.deleteWebhook(webhookID, guid);
|
|
363
|
-
console.log('Webhook deleted successfully');
|
|
364
|
-
return result;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
console.error('Failed to delete webhook:', error.message);
|
|
367
|
-
throw error;
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
// Bulk cleanup of inactive webhooks
|
|
372
|
-
const cleanupInactiveWebhooks = async () => {
|
|
373
|
-
const webhooks = await apiClient.webhookMethods.getWebhookList(guid);
|
|
374
|
-
const inactiveWebhooks = webhooks.filter(w => !w.isActive);
|
|
375
|
-
|
|
376
|
-
console.log(`Found ${inactiveWebhooks.length} inactive webhooks`);
|
|
377
|
-
|
|
378
|
-
for (const webhook of inactiveWebhooks) {
|
|
379
|
-
const shouldDelete = confirm(
|
|
380
|
-
`Delete inactive webhook "${webhook.name}"? (${webhook.url})`
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
if (shouldDelete) {
|
|
384
|
-
await apiClient.webhookMethods.deleteWebhook(webhook.webhookID, guid);
|
|
385
|
-
console.log(`Deleted webhook: ${webhook.name}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
// Delete webhooks with high failure rates
|
|
391
|
-
const cleanupFailingWebhooks = async (maxFailureRate = 50) => {
|
|
392
|
-
const webhooks = await apiClient.webhookMethods.getWebhookList(guid);
|
|
393
|
-
|
|
394
|
-
const failingWebhooks = webhooks.filter(webhook =>
|
|
395
|
-
webhook.statistics &&
|
|
396
|
-
webhook.statistics.successRate < maxFailureRate &&
|
|
397
|
-
webhook.statistics.totalDeliveries > 10 // Only consider webhooks with sufficient data
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
console.log(`Found ${failingWebhooks.length} webhooks with high failure rate`);
|
|
401
|
-
|
|
402
|
-
for (const webhook of failingWebhooks) {
|
|
403
|
-
console.log(`Webhook "${webhook.name}" has ${webhook.statistics.successRate}% success rate`);
|
|
404
|
-
|
|
405
|
-
const shouldDelete = confirm(
|
|
406
|
-
`Delete failing webhook "${webhook.name}"? (Success rate: ${webhook.statistics.successRate}%)`
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
if (shouldDelete) {
|
|
410
|
-
await apiClient.webhookMethods.deleteWebhook(webhook.webhookID, guid);
|
|
411
|
-
console.log(`Deleted failing webhook: ${webhook.name}`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
**Deletion Considerations:**
|
|
418
|
-
- Deleting a webhook immediately stops all future event deliveries
|
|
419
|
-
- Pending webhook deliveries may still be processed
|
|
420
|
-
- Webhook statistics and history are permanently lost
|
|
421
|
-
- Consider deactivating instead of deleting for temporary suspension
|
|
422
|
-
|
|
423
|
-
**Best Practices:**
|
|
424
|
-
- Test webhook endpoints before creating webhooks
|
|
425
|
-
- Use appropriate authentication to secure webhook endpoints
|
|
426
|
-
- Monitor webhook statistics and success rates
|
|
427
|
-
- Implement proper error handling in webhook receivers
|
|
428
|
-
- Use webhook signatures to verify authenticity
|
|
429
|
-
- Set reasonable timeout values to avoid blocking
|
|
430
|
-
- Consider implementing idempotency in webhook receivers
|
|
431
|
-
|
|
432
|
-
**Error Handling:**
|
|
433
|
-
- Throws `Exception` when webhook not found
|
|
434
|
-
- Throws `Exception` when deletion fails
|
|
435
|
-
- Throws `Exception` when insufficient permissions to delete webhooks
|
|
436
|
-
|
|
437
|
-
---
|
|
438
|
-
|
|
439
|
-
## Navigation
|
|
440
|
-
- [← Back to Main Documentation](../README.md)
|
|
441
|
-
- [Authentication & Setup](./auth.md)
|
|
442
|
-
- [AssetMethods](./asset-methods.md)
|
|
443
|
-
- [BatchMethods](./batch-methods.md)
|
|
444
|
-
- [ContainerMethods](./container-methods.md)
|
|
445
|
-
- [ContentMethods](./content-methods.md)
|
|
446
|
-
- [InstanceMethods](./instance-methods.md)
|
|
447
|
-
- [InstanceUserMethods](./instance-user-methods.md)
|
|
448
|
-
- [ModelMethods](./model-methods.md)
|
|
449
|
-
- [PageMethods](./page-methods.md)
|
|
450
|
-
- [ServerUserMethods](./server-user-methods.md)
|
|
451
|
-
- [ServerUserMethods](./server-user-methods.md)
|
|
452
|
-
- **WebhookMethods** *(current)*
|
|
1
|
+
# Agility CMS & Management API TypeScript SDK
|
|
2
|
+
|
|
3
|
+
## WebhookMethods
|
|
4
|
+
|
|
5
|
+
This class provides webhook management operations for Agility CMS. Webhooks deliver real-time HTTP POST notifications to your endpoints when content is saved, published, or moved through workflow, enabling integration with external systems and automated builds.
|
|
6
|
+
|
|
7
|
+
**Important Notes:**
|
|
8
|
+
- Webhooks fire on three event categories: content save, content publish, and content workflow events — each can be toggled per webhook
|
|
9
|
+
- Webhook IDs are string GUIDs (the `rowKey` of the stored record), not numbers
|
|
10
|
+
- Signed delivery is **opt-in per webhook** (`secureDeliveryEnabled`, off by default): when enabled, deliveries carry [Standard Webhooks](https://www.standardwebhooks.com/) signature headers — see [Verifying deliveries](#verifying-deliveries-signing) below. Webhooks with it off (including every webhook created before the feature shipped) are delivered unsigned, exactly as before
|
|
11
|
+
- List responses never include signing secrets; `getWebhook` masks the signing secret unless the caller has *FullControl* permission on webhooks
|
|
12
|
+
- Rotating a signing secret requires *FullControl* permission; reading delivery history requires *Manage* permission
|
|
13
|
+
- Failed deliveries are retried automatically when retries are enabled — see [Retry behavior](#retry-behavior)
|
|
14
|
+
|
|
15
|
+
### Function List
|
|
16
|
+
- [webhookList](#webhooklist) - Retrieves a paged list of webhooks
|
|
17
|
+
- [getWebhook](#getwebhook) - Retrieves a specific webhook by ID
|
|
18
|
+
- [saveWebhook](#savewebhook) - Creates or updates a webhook
|
|
19
|
+
- [deleteWebhook](#deletewebhook) - Deletes a webhook by ID
|
|
20
|
+
- [getWebhookHistory](#getwebhookhistory) - Retrieves the delivery history for a webhook
|
|
21
|
+
- [rotateWebhookSecret](#rotatewebhooksecret) - Rotates a webhook's signing secret
|
|
22
|
+
|
|
23
|
+
### Reference
|
|
24
|
+
- [The Webhook object](#the-webhook-object)
|
|
25
|
+
- [Verifying deliveries (signing)](#verifying-deliveries-signing)
|
|
26
|
+
- [Retry behavior](#retry-behavior)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### webhookList
|
|
31
|
+
|
|
32
|
+
Retrieves a paged list of webhooks for the instance. Uses continuation-token paging: pass the `token` from a previous response to fetch the next page. Signing secrets are always stripped from list responses.
|
|
33
|
+
|
|
34
|
+
| Parameter | Type | Required | Description |
|
|
35
|
+
|-----------|------|----------|-------------|
|
|
36
|
+
| `guid` | `string` | Yes | Current website GUID |
|
|
37
|
+
| `take` | `number` | No | Number of records to take (default: 20) |
|
|
38
|
+
| `token` | `string` | No | Continuation token from a previous response (default: null) |
|
|
39
|
+
|
|
40
|
+
**Returns:** `TokenPagedResult<Webhook>` - `{ token, items }` where `token` is the continuation token for the next page (or null when there are no more results) and `items` is the array of webhooks
|
|
41
|
+
|
|
42
|
+
**Usage Example:**
|
|
43
|
+
```typescript
|
|
44
|
+
// First page
|
|
45
|
+
let page = await apiClient.webhookMethods.webhookList(guid, 20);
|
|
46
|
+
page.items.forEach(webhook => {
|
|
47
|
+
console.log(`${webhook.name} -> ${webhook.url} (enabled: ${webhook.enabled})`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Fetch all pages
|
|
51
|
+
const allWebhooks = [...page.items];
|
|
52
|
+
while (page.token) {
|
|
53
|
+
page = await apiClient.webhookMethods.webhookList(guid, 20, page.token);
|
|
54
|
+
allWebhooks.push(...page.items);
|
|
55
|
+
}
|
|
56
|
+
console.log(`Total webhooks: ${allWebhooks.length}`);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Error Handling:**
|
|
60
|
+
- Throws `Exception` when retrieval fails
|
|
61
|
+
|
|
62
|
+
### getWebhook
|
|
63
|
+
|
|
64
|
+
Retrieves a specific webhook by its ID (the `rowKey` GUID).
|
|
65
|
+
|
|
66
|
+
| Parameter | Type | Required | Description |
|
|
67
|
+
|-----------|------|----------|-------------|
|
|
68
|
+
| `guid` | `string` | Yes | Current website GUID |
|
|
69
|
+
| `webhookID` | `string` | Yes | The webhook ID (`rowKey`) to retrieve |
|
|
70
|
+
|
|
71
|
+
**Returns:** `Webhook` - The webhook object. The `signingSecret` is masked unless the caller has *FullControl* permission on webhooks — the same bar as rotating it.
|
|
72
|
+
|
|
73
|
+
**Usage Example:**
|
|
74
|
+
```typescript
|
|
75
|
+
const webhook = await apiClient.webhookMethods.getWebhook(guid, webhookID);
|
|
76
|
+
console.log('Webhook name:', webhook.name);
|
|
77
|
+
console.log('Webhook URL:', webhook.url);
|
|
78
|
+
console.log('Enabled:', webhook.enabled);
|
|
79
|
+
console.log('Events:', {
|
|
80
|
+
save: webhook.contentSaveEvents,
|
|
81
|
+
publish: webhook.contentPublishEvents,
|
|
82
|
+
workflow: webhook.contentWorkflowEvents
|
|
83
|
+
});
|
|
84
|
+
console.log('Retries:', webhook.retriesEnabled
|
|
85
|
+
? `${webhook.retryCount} retries at ${webhook.retrySpeed} speed`
|
|
86
|
+
: 'disabled');
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Error Handling:**
|
|
90
|
+
- Throws `Exception` when the webhook is not found or retrieval fails
|
|
91
|
+
|
|
92
|
+
### saveWebhook
|
|
93
|
+
|
|
94
|
+
Creates a new webhook or updates an existing one.
|
|
95
|
+
|
|
96
|
+
**To update an existing webhook, the `partitionKey` and `rowKey` from a previously retrieved webhook must be present on the object** — without them the API treats the save as a create and mints a new webhook. The easiest pattern is get → modify → save.
|
|
97
|
+
|
|
98
|
+
Signed delivery is opt-in per webhook: set `secureDeliveryEnabled: true` and the server mints a signing secret on that save (webhooks with it off — including every webhook created before this feature — are delivered unsigned, exactly as before). Any `signingSecret` value you send is ignored; secrets are always server-controlled. Use [rotateWebhookSecret](#rotatewebhooksecret) to change one.
|
|
99
|
+
|
|
100
|
+
The response sets **`signingSecretJustCreated: true`** on exactly the save that mints the secret, so you can tell a newly created secret from one that was already there. It is a property of the response, never of the stored webhook — `getWebhook` never returns it. Note that flipping `secureDeliveryEnabled` off and back on does **not** mint a new secret: the original is retained, and the flag stays `false`.
|
|
101
|
+
|
|
102
|
+
| Parameter | Type | Required | Description |
|
|
103
|
+
|-----------|------|----------|-------------|
|
|
104
|
+
| `guid` | `string` | Yes | Current website GUID |
|
|
105
|
+
| `webhook` | `Webhook` | Yes | Webhook object to save |
|
|
106
|
+
|
|
107
|
+
**Returns:** `Webhook` - The saved webhook object
|
|
108
|
+
|
|
109
|
+
**Usage Example:**
|
|
110
|
+
```typescript
|
|
111
|
+
import { Webhook } from '@agility/management-sdk';
|
|
112
|
+
|
|
113
|
+
// Create a new webhook
|
|
114
|
+
const webhook = new Webhook();
|
|
115
|
+
webhook.name = 'Content Publish Notification';
|
|
116
|
+
webhook.url = 'https://api.example.com/webhooks/agility';
|
|
117
|
+
webhook.enabled = true;
|
|
118
|
+
webhook.contentSaveEvents = false;
|
|
119
|
+
webhook.contentPublishEvents = true;
|
|
120
|
+
webhook.contentWorkflowEvents = false;
|
|
121
|
+
webhook.retriesEnabled = true;
|
|
122
|
+
webhook.retryCount = 5;
|
|
123
|
+
webhook.retrySpeed = 'standard';
|
|
124
|
+
|
|
125
|
+
const saved = await apiClient.webhookMethods.saveWebhook(guid, webhook);
|
|
126
|
+
console.log('Webhook created with ID:', saved.rowKey);
|
|
127
|
+
|
|
128
|
+
// Update an existing webhook (partitionKey/rowKey carry through from the get)
|
|
129
|
+
const existing = await apiClient.webhookMethods.getWebhook(guid, saved.rowKey);
|
|
130
|
+
existing.name = 'Updated Webhook Name';
|
|
131
|
+
existing.contentWorkflowEvents = true;
|
|
132
|
+
|
|
133
|
+
const updated = await apiClient.webhookMethods.saveWebhook(guid, existing);
|
|
134
|
+
console.log('Webhook updated:', updated.name);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Error Handling:**
|
|
138
|
+
- Throws `Exception` when validation or the save operation fails
|
|
139
|
+
|
|
140
|
+
### deleteWebhook
|
|
141
|
+
|
|
142
|
+
Deletes a webhook by its ID. Deletion immediately stops future deliveries for that webhook, and also removes that webhook's delivery history — history is keyed by webhook, so it is not retrievable once the webhook is gone. Export anything you need first.
|
|
143
|
+
|
|
144
|
+
| Parameter | Type | Required | Description |
|
|
145
|
+
|-----------|------|----------|-------------|
|
|
146
|
+
| `guid` | `string` | Yes | Current website GUID |
|
|
147
|
+
| `webhookID` | `string` | Yes | The webhook ID (`rowKey`) to delete |
|
|
148
|
+
|
|
149
|
+
**Returns:** `void`
|
|
150
|
+
|
|
151
|
+
**Usage Example:**
|
|
152
|
+
```typescript
|
|
153
|
+
await apiClient.webhookMethods.deleteWebhook(guid, webhookID);
|
|
154
|
+
console.log('Webhook deleted');
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Error Handling:**
|
|
158
|
+
- Throws `Exception` when the webhook is not found or deletion fails
|
|
159
|
+
|
|
160
|
+
### getWebhookHistory
|
|
161
|
+
|
|
162
|
+
Retrieves the delivery history for a webhook — one record per event delivery, including the payload, HTTP response, and retry state. Requires *Manage* permission on webhooks.
|
|
163
|
+
|
|
164
|
+
Dates are UTC dates. The date range may span at most **366 days**; when no range is given, the **last 7 days** are returned. `take` is capped at **100** per page; use the continuation `token` for more.
|
|
165
|
+
|
|
166
|
+
Records come back **newest first**, and that ordering holds *across* pages — history is stored newest-first, so paging walks it in order rather than sorting each page in isolation. Treat `token` as opaque: it identifies a position in the sequence and is only meaningful for the same `webhookID` and date range.
|
|
167
|
+
|
|
168
|
+
| Parameter | Type | Required | Description |
|
|
169
|
+
|-----------|------|----------|-------------|
|
|
170
|
+
| `guid` | `string` | Yes | Current website GUID |
|
|
171
|
+
| `webhookID` | `string` | Yes | The webhook ID (`rowKey`) whose history to retrieve |
|
|
172
|
+
| `options` | `object` | No | Optional query options (below) |
|
|
173
|
+
| `options.fromDate` | `string` | No | UTC start date (e.g. `2026-08-01`) |
|
|
174
|
+
| `options.toDate` | `string` | No | UTC end date; range max 366 days |
|
|
175
|
+
| `options.take` | `number` | No | Records per page, max 100 |
|
|
176
|
+
| `options.token` | `string` | No | Continuation token from a previous response |
|
|
177
|
+
|
|
178
|
+
**Returns:** `TokenPagedResult<WebhookHistory>` - `{ token, items }` with the continuation token and the history records
|
|
179
|
+
|
|
180
|
+
**Usage Example:**
|
|
181
|
+
```typescript
|
|
182
|
+
// Last 7 days (default)
|
|
183
|
+
const history = await apiClient.webhookMethods.getWebhookHistory(guid, webhookID);
|
|
184
|
+
history.items.forEach(entry => {
|
|
185
|
+
console.log(`${entry.sendDate} -> HTTP ${entry.httpResponseCode} (success: ${entry.success})`);
|
|
186
|
+
if (!entry.success) {
|
|
187
|
+
console.log(` attempts: ${entry.attemptCount}, next attempt: ${entry.nextAttemptUtc}`);
|
|
188
|
+
console.log(` last error: ${entry.lastError}`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// A specific window, paging through all records
|
|
193
|
+
let page = await apiClient.webhookMethods.getWebhookHistory(guid, webhookID, {
|
|
194
|
+
fromDate: '2026-08-01',
|
|
195
|
+
toDate: '2026-08-15',
|
|
196
|
+
take: 100
|
|
197
|
+
});
|
|
198
|
+
const failures = page.items.filter(e => !e.success);
|
|
199
|
+
while (page.token) {
|
|
200
|
+
page = await apiClient.webhookMethods.getWebhookHistory(guid, webhookID, {
|
|
201
|
+
fromDate: '2026-08-01',
|
|
202
|
+
toDate: '2026-08-15',
|
|
203
|
+
take: 100,
|
|
204
|
+
token: page.token
|
|
205
|
+
});
|
|
206
|
+
failures.push(...page.items.filter(e => !e.success));
|
|
207
|
+
}
|
|
208
|
+
console.log(`Failed deliveries in window: ${failures.length}`);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**WebhookHistory Properties:**
|
|
212
|
+
- `partitionKey` / `rowKey`: Storage identity of the history record. **`rowKey` is the value sent as the `webhook-id` header**, so use it to correlate a history record with what your endpoint received. Both are opaque — treat them as strings and don't parse them; the internal format is not part of this contract
|
|
213
|
+
- `webhookRowKey`: The ID of the webhook this delivery belongs to
|
|
214
|
+
- `eventKey`: Identifier of the triggering *event*, shared by every webhook that received it (used server-side for de-duplication). This is **not** the `webhook-id` header — correlate with `rowKey` instead
|
|
215
|
+
- `queuedDate` / `sendDate`: When the delivery was queued and sent
|
|
216
|
+
- `url`: The URL the payload was POSTed to
|
|
217
|
+
- `instanceGuid`: The instance the event originated from
|
|
218
|
+
- `contentSaveEvent` / `contentPublishEvent` / `contentWorkflowEvent`: Which event category fired
|
|
219
|
+
- `payload`: The JSON payload that was sent
|
|
220
|
+
- `httpResponseCode` / `success` / `responseText`: The endpoint's response (any 2xx counts as success)
|
|
221
|
+
- `attemptCount` / `lastAttemptDate` / `nextAttemptUtc` / `lastError`: Retry state for failed deliveries. `attemptCount > 1` means the record is a retry; a failed record with no `nextAttemptUtc` will not be attempted again
|
|
222
|
+
- `signed` / `signatureKeyCount`: Whether the last attempt went out with signature headers, and how many signatures it carried (0 unsigned, 1 normally, 2 inside the 24-hour window after a secret roll). Recorded at send time, so it reports what *that* delivery did — it is not the webhook's current `secureDeliveryEnabled`, which can change at any time. `signed` is `null` when there is no record: the delivery has not been attempted yet, or the record predates the field
|
|
223
|
+
|
|
224
|
+
**Error Handling:**
|
|
225
|
+
- Throws `Exception` when retrieval fails (including insufficient permissions or an invalid date range)
|
|
226
|
+
|
|
227
|
+
### rotateWebhookSecret
|
|
228
|
+
|
|
229
|
+
Rotates the webhook's signing secret. Requires *FullControl* permission on webhooks.
|
|
230
|
+
|
|
231
|
+
The response is the only place the new secret is returned in full — store it immediately. The previous secret is kept as `previousSigningSecret` so deliveries signed during the rollover window can still be verified; update your endpoint to the new secret promptly.
|
|
232
|
+
|
|
233
|
+
| Parameter | Type | Required | Description |
|
|
234
|
+
|-----------|------|----------|-------------|
|
|
235
|
+
| `guid` | `string` | Yes | Current website GUID |
|
|
236
|
+
| `webhookID` | `string` | Yes | The webhook ID (`rowKey`) whose secret to rotate |
|
|
237
|
+
|
|
238
|
+
**Returns:** `Webhook` - The webhook with the new, unmasked `signingSecret`, the old secret in `previousSigningSecret`, and `secretRolledUtc` set to the rotation time
|
|
239
|
+
|
|
240
|
+
**Usage Example:**
|
|
241
|
+
```typescript
|
|
242
|
+
const rotated = await apiClient.webhookMethods.rotateWebhookSecret(guid, webhookID);
|
|
243
|
+
console.log('New signing secret:', rotated.signingSecret); // whsec_...
|
|
244
|
+
console.log('Rotated at:', rotated.secretRolledUtc);
|
|
245
|
+
|
|
246
|
+
// Store the new secret in your secret manager, then update your endpoint's verifier.
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Error Handling:**
|
|
250
|
+
- Throws `Exception` when rotation fails (including insufficient permissions)
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## The Webhook object
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
class Webhook {
|
|
258
|
+
partitionKey: string | null; // Storage identity — required (with rowKey) to update an existing webhook
|
|
259
|
+
rowKey: string | null; // The webhook's ID (GUID); used as webhookID in the other methods
|
|
260
|
+
timestamp?: string | null; // Storage timestamp (read-only)
|
|
261
|
+
eTag?: string | null; // Storage ETag (read-only)
|
|
262
|
+
name: string | null; // Human-readable name
|
|
263
|
+
url: string | null; // Target URL for deliveries
|
|
264
|
+
instanceGuid: string | null; // Instance the webhook belongs to
|
|
265
|
+
enabled: boolean; // Whether the webhook fires at all
|
|
266
|
+
contentWorkflowEvents: boolean; // Fire on workflow events (request approval, approve, decline)
|
|
267
|
+
contentPublishEvents: boolean; // Fire on publish/unpublish events
|
|
268
|
+
contentSaveEvents: boolean; // Fire on save/delete events
|
|
269
|
+
secureDeliveryEnabled: boolean; // Opt-in for signed delivery (default false; existing webhooks stay unsigned)
|
|
270
|
+
signingSecret: string | null; // Standard Webhooks secret (whsec_...) — server-controlled, masked without FullControl permission
|
|
271
|
+
previousSigningSecret: string | null; // Prior secret, kept for the rotation rollover window
|
|
272
|
+
secretRolledUtc: string | null; // When the secret was last rotated
|
|
273
|
+
retriesEnabled: boolean; // Whether failed deliveries are retried
|
|
274
|
+
retryCount: number; // Retries after the initial attempt (1-8)
|
|
275
|
+
retrySpeed: "fast" | "standard" | "slow" | null; // Base retry delay tier
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Verifying deliveries (signing)
|
|
280
|
+
|
|
281
|
+
Deliveries for a webhook with `secureDeliveryEnabled: true` are signed per the [Standard Webhooks](https://www.standardwebhooks.com/) specification; those carry three extra headers. Deliveries for webhooks without it carry no signature headers at all, so verify signatures only for endpoints you have enabled secure delivery on:
|
|
282
|
+
|
|
283
|
+
| Header | Description |
|
|
284
|
+
|--------|-------------|
|
|
285
|
+
| `webhook-id` | Unique ID of this delivery (the history record's `rowKey`) — reuse it as an **idempotency key**, since retries resend the same `webhook-id` |
|
|
286
|
+
| `webhook-timestamp` | Unix timestamp (seconds) of the delivery attempt |
|
|
287
|
+
| `webhook-signature` | `v1,<base64 signature>` — HMAC-SHA256 over `{id}.{timestamp}.{body}` keyed with the decoded secret |
|
|
288
|
+
|
|
289
|
+
The signing secret is the webhook's `signingSecret` (`whsec_` followed by the base64-encoded key). Verify with any Standard Webhooks library rather than hand-rolling the HMAC:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { Webhook as StandardWebhook } from 'standardwebhooks';
|
|
293
|
+
|
|
294
|
+
const verifier = new StandardWebhook(process.env.AGILITY_WEBHOOK_SECRET); // whsec_...
|
|
295
|
+
|
|
296
|
+
app.post('/webhooks/agility', (req, res) => {
|
|
297
|
+
try {
|
|
298
|
+
// verify() checks the signature AND enforces the timestamp tolerance
|
|
299
|
+
const payload = verifier.verify(req.rawBody, req.headers);
|
|
300
|
+
|
|
301
|
+
// Use webhook-id for idempotency — retries resend the same id
|
|
302
|
+
if (alreadyProcessed(req.headers['webhook-id'])) {
|
|
303
|
+
return res.sendStatus(200);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
processEvent(payload);
|
|
307
|
+
res.sendStatus(200); // any 2xx marks the delivery successful
|
|
308
|
+
} catch {
|
|
309
|
+
res.sendStatus(401);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Verification rules:**
|
|
315
|
+
- Reject deliveries whose `webhook-timestamp` is more than **5 minutes** from now (standard libraries enforce this) — this bounds replay attacks
|
|
316
|
+
- During a secret rotation, accept signatures from either the new secret or `previousSigningSecret`
|
|
317
|
+
- Respond with a **2xx** status to acknowledge; anything else (or a timeout) counts as a failure and triggers retries when enabled
|
|
318
|
+
|
|
319
|
+
## Retry behavior
|
|
320
|
+
|
|
321
|
+
Retries apply per delivery and are configured on the webhook:
|
|
322
|
+
|
|
323
|
+
- `retriesEnabled` — master switch; when false, one attempt is made and failures are recorded but never retried
|
|
324
|
+
- `retryCount` — how many retries are made **after the initial attempt**, from 1 to 8
|
|
325
|
+
- `retrySpeed` — the base delay before the first retry:
|
|
326
|
+
|
|
327
|
+
| Speed | Base delay |
|
|
328
|
+
|-------|-----------|
|
|
329
|
+
| `fast` | 30 seconds |
|
|
330
|
+
| `standard` | 5 minutes |
|
|
331
|
+
| `slow` | 30 minutes |
|
|
332
|
+
|
|
333
|
+
Each subsequent retry backs off exponentially at **×4** the previous delay, with jitter, capped at **24 hours** between attempts. For example, `standard` retries land at roughly 5m, 20m, 80m, ... after the failure.
|
|
334
|
+
|
|
335
|
+
A delivery is successful as soon as the endpoint returns any **2xx** response; success stops the retry chain. Retry state for a delivery is visible in [getWebhookHistory](#getwebhookhistory) via `attemptCount`, `lastAttemptDate`, `nextAttemptUtc`, and `lastError`.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Navigation
|
|
340
|
+
- [← Back to Main Documentation](../README.md)
|
|
341
|
+
- [Authentication & Setup](./auth.md)
|
|
342
|
+
- [AssetMethods](./asset-methods.md)
|
|
343
|
+
- [BatchMethods](./batch-methods.md)
|
|
344
|
+
- [ContainerMethods](./container-methods.md)
|
|
345
|
+
- [ContentMethods](./content-methods.md)
|
|
346
|
+
- [InstanceMethods](./instance-methods.md)
|
|
347
|
+
- [InstanceUserMethods](./instance-user-methods.md)
|
|
348
|
+
- [ModelMethods](./model-methods.md)
|
|
349
|
+
- [PageMethods](./page-methods.md)
|
|
350
|
+
- [ServerUserMethods](./server-user-methods.md)
|
|
351
|
+
- **WebhookMethods** *(current)*
|