# Webhooks

> Source: https://agilitycms.com/docs/javascript/management-sdk/webhooks

Webhooks let external systems react to activity in an Agility instance — content being published, content being saved, and content moving through workflow. Instead of polling the API, you register a URL that Agility calls when those events happen. The Management SDK exposes `webhookMethods` so you can list, inspect, create, and remove those registrations programmatically.

> **JavaScript / TypeScript SDK only.** Webhook management is currently available in the JavaScript/TypeScript Management SDK only — the .NET SDK does not include webhook methods. If you are working in .NET, manage webhooks in the Agility UI or call the Management API directly.

> **Secure delivery, retries and delivery history require `@agility/management-sdk` 0.1.40 or later.**

All of the methods below take the instance `guid` as their first argument and are available on `apiClient.webhookMethods`.

## List webhooks

Returns the webhooks registered for the instance. Results are paged.

```ts
//list the webhooks for the instance
const webhooks = await apiClient.webhookMethods.webhookList(guid);
console.log("Webhooks for Instance:");
console.log(webhooks);
```

`webhookList(guid, take = 20, token = null)` — pass the `token` from the previous result to fetch the next page.

> Signing secrets are never returned by `webhookList`. Use `getWebhook` to read one, and see the permission note below.

## Get a webhook

Retrieves a single webhook by its `id`.

```ts
//get a specific webhook
const webhook = await apiClient.webhookMethods.getWebhook(guid, webhooks[0].id);
console.log("Webhook Details:");
console.log(webhook);
```

> **Reading a signing secret requires Full Control.** For callers with a lower permission level the secret is masked. The webhook itself is still returned and fully manageable.

## Save (create) a webhook

Pass the instance `guid` and a webhook object. The saved webhook is returned, including its `id`.

```ts
//create a new webhook
const newWebhook = await apiClient.webhookMethods.saveWebhook(guid, {
	name: "My New Webhook",
	url: "https://example.com/webhook",
	contentPublishEvents: true,
	contentSaveEvents: true,
	contentWorkflowEvents: true,
	enabled: true,
	instanceGuid: guid,
});
```

### `saveWebhook` payload

| Field | Type | Description |
| --- | --- | --- |
| `name` | string | Display name for the webhook. |
| `url` | string | The endpoint Agility calls when a subscribed event fires. |
| `contentPublishEvents` | boolean | Subscribe to content publish events. |
| `contentSaveEvents` | boolean | Subscribe to content save events. |
| `contentWorkflowEvents` | boolean | Subscribe to content workflow events. |
| `enabled` | boolean | Whether the webhook is active. |
| `instanceGuid` | string | The GUID of the instance the webhook belongs to. |
| `secureDeliveryEnabled` | boolean | Sign every delivery so your endpoint can verify it. Defaults to `false`. See [Verifying Signed Webhooks](/docs/developers/verifying-signed-webhooks). |
| `retriesEnabled` | boolean | Retry a failed delivery. Defaults to `false`. |
| `retryCount` | number | How many attempts, 1–8. Defaults to `3`. Only used when `retriesEnabled` is `true`. |
| `retrySpeed` | `"fast"` \| `"standard"` \| `"slow"` | Base interval between attempts — 30 seconds, 5 minutes or 30 minutes, growing exponentially with jitter. |

The server controls the signing secret. Any `signingSecret` you send is ignored.

### Turning on secure delivery

Set `secureDeliveryEnabled` and save. Agility mints the secret and returns it on that response only, with `signingSecretJustCreated: true` — store it then, because a later `getWebhook` will not repeat it as a new value.

```ts
//enable secure delivery and capture the minted secret
const saved = await apiClient.webhookMethods.saveWebhook(guid, {
	...webhook,
	secureDeliveryEnabled: true,
});

if (saved.signingSecretJustCreated) {
	console.log("Store this secret:", saved.signingSecret); // whsec_...
}
```

## Get delivery history

Returns delivery attempts for a webhook, newest first. Defaults to the last 7 days; the range cannot exceed 366 days and `take` maxes out at 100. History is retained for 90 days.

```ts
//read the recent delivery history for a webhook
const history = await apiClient.webhookMethods.getWebhookHistory(guid, webhookID, {
	take: 50,
});

for (const attempt of history.items) {
	console.log(attempt.sendDate, attempt.httpResponseCode, attempt.success);
}

//fetch the next page
if (history.token) {
	const next = await apiClient.webhookMethods.getWebhookHistory(guid, webhookID, {
		take: 50,
		token: history.token,
	});
}
```

`getWebhookHistory(guid, webhookID, options?)` accepts `fromDate`, `toDate`, `take` and `token`, and returns a `TokenPagedResult<WebhookHistory>` — `items` plus a `token` for the next page.

Useful fields on each entry:

| Field | Description |
| --- | --- |
| `sendDate` | When the delivery succeeded. Empty while a delivery is still being retried. |
| `success` | Whether the attempt succeeded. |
| `httpResponseCode` / `responseText` | What your endpoint returned. Response text is truncated. |
| `attemptCount` | Attempts made so far. |
| `nextAttemptUtc` | When the next retry is scheduled, if there is one. |
| `lastError` | The failure reason for the most recent attempt. |
| `signed` | Whether the last attempt carried signature headers. `null` means it has not been attempted yet. |
| `payload` | The body that was sent. |

> **Delivery history is go-forward only.** Deliveries from before this feature shipped are not returned, so a webhook you created earlier will show an empty history until it fires again.

## Roll a signing secret

Generates a new signing secret immediately and returns it.

```ts
//roll the signing secret for a webhook
const rolled = await apiClient.webhookMethods.rotateWebhookSecret(guid, webhookID);
console.log("New secret:", rolled.signingSecret);
```

For 24 hours afterwards Agility signs each delivery with **both** the new and the previous secret, sending two space-separated signatures. That gives you a window to deploy the new secret without dropping deliveries. Verification code that accepts any matching signature in the header needs no change at all.

> Rolling a secret requires **Full Control**.

## Delete a webhook

Removes a webhook by its `id`.

```ts
//delete a webhook
await apiClient.webhookMethods.deleteWebhook(guid, newWebhook.id);
```

Deleting a webhook also removes its delivery history.
