@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.
@@ -1,452 +1,351 @@
1
- # Agility CMS & Management API TypeScript SDK
2
-
3
- ## WebhookMethods
4
-
5
- This class provides comprehensive webhook management operations for Agility CMS. Webhooks allow you to receive real-time notifications when specific events occur in your CMS, enabling integration with external systems and automated workflows.
6
-
7
- **Important Notes:**
8
- - Webhooks provide real-time event notifications to external endpoints
9
- - Each webhook can be configured to trigger on specific events
10
- - Webhooks support various event types (content changes, page updates, etc.)
11
- - Webhook URLs must be accessible and return appropriate HTTP status codes
12
- - Failed webhook deliveries are automatically retried with exponential backoff
13
- - Webhooks can be secured with authentication headers or signatures
14
-
15
- ### Function List
16
- - [getWebhook](#getwebhook) - Retrieves a specific webhook by ID
17
- - [webhookList](#webhooklist) - Retrieves list of all webhooks
18
- - [saveWebhook](#savewebhook) - Creates or updates a webhook
19
- - [deleteWebhook](#deletewebhook) - Deletes a webhook by ID
20
-
21
- ---
22
-
23
- ### getWebhook
24
-
25
- Retrieves a specific webhook by its unique ID.
26
-
27
- | Parameter | Type | Required | Description |
28
- |-----------|------|----------|-------------|
29
- | `webhookID` | `number` | Yes | The webhook ID to retrieve |
30
- | `guid` | `string` | Yes | Current website GUID |
31
-
32
- **Returns:** `Webhook` - Complete webhook object with configuration and statistics
33
-
34
- **Usage Example:**
35
- ```typescript
36
- const webhook = await apiClient.webhookMethods.getWebhook(123, guid);
37
- console.log('Webhook name:', webhook.name);
38
- console.log('Webhook URL:', webhook.url);
39
- console.log('Events:', webhook.events);
40
- console.log('Active:', webhook.isActive);
41
- console.log('Created:', webhook.createdDate);
42
- console.log('Last triggered:', webhook.lastTriggered);
43
-
44
- // Display webhook statistics
45
- if (webhook.statistics) {
46
- console.log('Statistics:');
47
- console.log(` Total deliveries: ${webhook.statistics.totalDeliveries}`);
48
- console.log(` Successful deliveries: ${webhook.statistics.successfulDeliveries}`);
49
- console.log(` Failed deliveries: ${webhook.statistics.failedDeliveries}`);
50
- console.log(` Success rate: ${webhook.statistics.successRate}%`);
51
- }
52
-
53
- // Display webhook configuration
54
- console.log('Configuration:');
55
- console.log(` Method: ${webhook.method}`);
56
- console.log(` Content Type: ${webhook.contentType}`);
57
- console.log(` Timeout: ${webhook.timeout}ms`);
58
- console.log(` Retry count: ${webhook.retryCount}`);
59
-
60
- // Check authentication
61
- if (webhook.authentication) {
62
- console.log('Authentication enabled:');
63
- console.log(` Type: ${webhook.authentication.type}`);
64
- console.log(` Headers: ${Object.keys(webhook.authentication.headers).join(', ')}`);
65
- } else {
66
- console.log('No authentication configured');
67
- }
68
- ```
69
-
70
- **Response Properties:**
71
- The `Webhook` object includes:
72
- - `webhookID`: Unique identifier for the webhook
73
- - `name`: Human-readable name for the webhook
74
- - `url`: Target URL for webhook deliveries
75
- - `events`: Array of events that trigger the webhook
76
- - `isActive`: Whether the webhook is active
77
- - `method`: HTTP method (GET, POST, PUT, etc.)
78
- - `contentType`: Content type for webhook payload
79
- - `timeout`: Request timeout in milliseconds
80
- - `retryCount`: Number of retry attempts for failed deliveries
81
- - `authentication`: Authentication configuration
82
- - `statistics`: Delivery statistics and metrics
83
-
84
- **Error Handling:**
85
- - Throws `Exception` when webhook not found
86
-
87
- ### webhookList
88
-
89
- Retrieves a list of all webhooks in the current website.
90
-
91
- | Parameter | Type | Required | Description |
92
- |-----------|------|----------|-------------|
93
- | `guid` | `string` | Yes | Current website GUID |
94
- | `take` | `number` | No | Number of records to take (default: 20) |
95
- | `token` | `string` | No | Token parameter (default: null) |
96
-
97
- **Returns:** `any` - Array of all webhooks
98
-
99
- **Usage Example:**
100
- ```typescript
101
- const webhooks = await apiClient.webhookMethods.getWebhookList(guid);
102
- console.log(`Total webhooks: ${webhooks.length}`);
103
-
104
- // Display webhook summary
105
- webhooks.forEach(webhook => {
106
- console.log(`Webhook: ${webhook.name}`);
107
- console.log(` URL: ${webhook.url}`);
108
- console.log(` Events: ${webhook.events.join(', ')}`);
109
- console.log(` Active: ${webhook.isActive ? 'Yes' : 'No'}`);
110
- console.log(` Last triggered: ${webhook.lastTriggered || 'Never'}`);
111
- console.log('---');
112
- });
113
-
114
- // Filter active webhooks
115
- const activeWebhooks = webhooks.filter(w => w.isActive);
116
- const inactiveWebhooks = webhooks.filter(w => !w.isActive);
117
- console.log(`Active webhooks: ${activeWebhooks.length}`);
118
- console.log(`Inactive webhooks: ${inactiveWebhooks.length}`);
119
-
120
- // Group webhooks by event type
121
- const webhooksByEvent = webhooks.reduce((acc, webhook) => {
122
- webhook.events.forEach(event => {
123
- if (!acc[event]) acc[event] = [];
124
- acc[event].push(webhook);
125
- });
126
- return acc;
127
- }, {});
128
-
129
- console.log('Webhooks by event:');
130
- Object.entries(webhooksByEvent).forEach(([event, eventWebhooks]) => {
131
- console.log(` ${event}: ${eventWebhooks.length} webhooks`);
132
- });
133
-
134
- // Find webhooks with high failure rates
135
- const failingWebhooks = webhooks.filter(webhook =>
136
- webhook.statistics && webhook.statistics.successRate < 90
137
- );
138
- console.log(`Webhooks with high failure rate: ${failingWebhooks.length}`);
139
-
140
- // Create webhook dropdown options
141
- const webhookOptions = webhooks.map(webhook => ({
142
- value: webhook.webhookID,
143
- label: webhook.name,
144
- url: webhook.url,
145
- active: webhook.isActive
146
- }));
147
- ```
148
-
149
- **Common Webhook Events:**
150
- - `content.created`: New content item created
151
- - `content.updated`: Content item updated
152
- - `content.deleted`: Content item deleted
153
- - `content.published`: Content item published
154
- - `content.unpublished`: Content item unpublished
155
- - `page.created`: New page created
156
- - `page.updated`: Page updated
157
- - `page.deleted`: Page deleted
158
- - `page.published`: Page published
159
- - `page.unpublished`: Page unpublished
160
- - `model.created`: Content model created
161
- - `model.updated`: Content model updated
162
- - `asset.uploaded`: Asset uploaded
163
- - `asset.updated`: Asset updated
164
-
165
- **Error Handling:**
166
- - Throws `Exception` when retrieval fails
167
-
168
- ### saveWebhook
169
-
170
- Creates a new webhook or updates an existing one.
171
-
172
- | Parameter | Type | Required | Description |
173
- |-----------|------|----------|-------------|
174
- | `webhook` | `Webhook` | Yes | Webhook object to save |
175
- | `guid` | `string` | Yes | Current website GUID |
176
-
177
- **Returns:** `Webhook` - Saved webhook object with updated metadata
178
-
179
- **Usage Example:**
180
- ```typescript
181
- // Create a new webhook
182
- const newWebhook = {
183
- name: 'Content Update Notification',
184
- url: 'https://api.example.com/webhooks/agility-content',
185
- events: [
186
- 'content.created',
187
- 'content.updated',
188
- 'content.published'
189
- ],
190
- isActive: true,
191
- method: 'POST',
192
- contentType: 'application/json',
193
- timeout: 30000,
194
- retryCount: 3,
195
- authentication: {
196
- type: 'header',
197
- headers: {
198
- 'Authorization': 'Bearer your-api-token',
199
- 'X-API-Key': 'your-api-key'
200
- }
201
- }
202
- };
203
-
204
- const savedWebhook = await apiClient.webhookMethods.saveWebhook(newWebhook, guid);
205
- console.log('Webhook created with ID:', savedWebhook.webhookID);
206
-
207
- // Update existing webhook
208
- const existingWebhook = await apiClient.webhookMethods.getWebhook(123, guid);
209
- existingWebhook.name = 'Updated Webhook Name';
210
- existingWebhook.url = 'https://api.example.com/webhooks/updated-endpoint';
211
- existingWebhook.events.push('page.published');
212
-
213
- const updatedWebhook = await apiClient.webhookMethods.saveWebhook(existingWebhook, guid);
214
- console.log('Webhook updated:', updatedWebhook.name);
215
-
216
- // Create webhook with custom payload template
217
- const customWebhook = {
218
- name: 'Custom Payload Webhook',
219
- url: 'https://api.example.com/webhooks/custom',
220
- events: ['content.published'],
221
- isActive: true,
222
- method: 'POST',
223
- contentType: 'application/json',
224
- timeout: 15000,
225
- retryCount: 2,
226
- payloadTemplate: {
227
- eventType: '{{event}}',
228
- timestamp: '{{timestamp}}',
229
- contentData: {
230
- id: '{{content.id}}',
231
- title: '{{content.title}}',
232
- referenceName: '{{content.referenceName}}'
233
- },
234
- customField: 'custom value'
235
- }
236
- };
237
-
238
- // Create webhook with signature verification
239
- const secureWebhook = {
240
- name: 'Secure Webhook',
241
- url: 'https://api.example.com/webhooks/secure',
242
- events: ['content.created', 'content.updated'],
243
- isActive: true,
244
- method: 'POST',
245
- contentType: 'application/json',
246
- timeout: 30000,
247
- retryCount: 3,
248
- authentication: {
249
- type: 'signature',
250
- secret: 'your-webhook-secret',
251
- algorithm: 'sha256',
252
- headerName: 'X-Agility-Signature'
253
- }
254
- };
255
-
256
- // Bulk webhook operations
257
- const createContentWebhooks = async (endpoints) => {
258
- const webhooks = [];
259
- for (const endpoint of endpoints) {
260
- const webhook = {
261
- name: `Content Webhook - ${endpoint.name}`,
262
- url: endpoint.url,
263
- events: ['content.created', 'content.updated', 'content.published'],
264
- isActive: true,
265
- method: 'POST',
266
- contentType: 'application/json',
267
- timeout: 30000,
268
- retryCount: 3
269
- };
270
-
271
- const saved = await apiClient.webhookMethods.saveWebhook(webhook, guid);
272
- webhooks.push(saved);
273
- }
274
- return webhooks;
275
- };
276
- ```
277
-
278
- **Webhook Structure:**
279
- ```typescript
280
- interface Webhook {
281
- webhookID?: number; // Auto-generated for new webhooks
282
- name: string; // Human-readable name (required)
283
- url: string; // Target URL (required)
284
- events: string[]; // Array of event types (required)
285
- isActive: boolean; // Whether webhook is active
286
- method: string; // HTTP method (GET, POST, PUT, etc.)
287
- contentType: string; // Content type for payload
288
- timeout: number; // Request timeout in milliseconds
289
- retryCount: number; // Number of retry attempts
290
- authentication?: WebhookAuth; // Authentication configuration
291
- payloadTemplate?: any; // Custom payload template
292
- statistics?: WebhookStats; // Delivery statistics (read-only)
293
- createdDate?: string; // Auto-generated
294
- lastTriggered?: string; // Auto-generated
295
- }
296
-
297
- interface WebhookAuth {
298
- type: string; // 'header', 'signature', 'basic', etc.
299
- headers?: { [key: string]: string }; // Custom headers
300
- secret?: string; // Secret for signature authentication
301
- algorithm?: string; // Algorithm for signature (sha256, etc.)
302
- headerName?: string; // Header name for signature
303
- }
304
-
305
- interface WebhookStats {
306
- totalDeliveries: number; // Total delivery attempts
307
- successfulDeliveries: number; // Successful deliveries
308
- failedDeliveries: number; // Failed deliveries
309
- successRate: number; // Success rate percentage
310
- lastDeliveryDate: string; // Last delivery attempt date
311
- lastSuccessDate: string; // Last successful delivery date
312
- }
313
- ```
314
-
315
- **Authentication Types:**
316
- - `header`: Custom headers for authentication
317
- - `signature`: HMAC signature verification
318
- - `basic`: Basic authentication
319
- - `bearer`: Bearer token authentication
320
- - `custom`: Custom authentication scheme
321
-
322
- **Error Handling:**
323
- - Throws `Exception` when validation fails (invalid URL, unsupported events, etc.)
324
- - Throws `Exception` when save operation fails
325
- - Throws `Exception` when authentication configuration is invalid
326
-
327
- ### deleteWebhook
328
-
329
- Deletes a webhook by its ID.
330
-
331
- | Parameter | Type | Required | Description |
332
- |-----------|------|----------|-------------|
333
- | `webhookID` | `number` | Yes | The webhook ID to delete |
334
- | `guid` | `string` | Yes | Current website GUID |
335
-
336
- **Returns:** `string` - Confirmation message of successful deletion
337
-
338
- **Usage Example:**
339
- ```typescript
340
- try {
341
- const result = await apiClient.webhookMethods.deleteWebhook(123, guid);
342
- console.log(result); // "Webhook deleted successfully"
343
- } catch (error) {
344
- console.error('Failed to delete webhook:', error.message);
345
- }
346
-
347
- // Safe webhook deletion with confirmation
348
- const safeDeleteWebhook = async (webhookID) => {
349
- try {
350
- // Get webhook details first
351
- const webhook = await apiClient.webhookMethods.getWebhook(webhookID, guid);
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)*