What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
SDKs
Create, update, list and delete webhooks, turn on signed delivery and retries, rotate signing secrets and read delivery history with the Agility CMS .NET Management SDK 2.0.
client.Webhooks manages an instance's webhooks: creating and changing them, their signing secrets, and their delivery history.
Note: Webhook management is new in 2.0. If you're upgrading from 1.x, see Migrating to 2.0.
This article covers the SDK calls. For how webhooks behave (the events, the payload, signed delivery, retries and delivery history), see Webhooks. For every event and its payload, the request headers and the delivery history fields in one place, see the Webhook Events and Payload Reference. To check signatures in the endpoint that receives deliveries, see Verifying Signed Webhooks.
The examples assume a client created as shown in the Intro, and guid set to your instance GUID. The webhook types are in Agility.Management.Sdk.Models:
using Agility.Management.Sdk;
using Agility.Management.Sdk.Models;
A webhook's ID is its RowKey, a string. Every method also takes an optional CancellationToken cancellationToken as its last parameter. The signatures below leave it out.
SaveWebhookAsync creates a webhook when RowKey is empty, and returns the saved webhook.
Webhook webhook = await client.Webhooks.SaveWebhookAsync(guid, new Webhook
{
Name = "Rebuild site",
Url = "https://build.example.com/hooks/agility",
Enabled = true,
ContentPublishEvents = true,
ContentSaveEvents = false,
ContentWorkflowEvents = false,
SecureDeliveryEnabled = true, // sign every delivery
});
if (webhook.SigningSecretJustCreated)
{
StoreSecret(webhook.SigningSecret!);
}
Console.WriteLine($"Created webhook {webhook.RowKey}");
Choose which events the webhook receives:
| Property | Sends a delivery when |
|---|---|
ContentPublishEvents | A content item or page is published or unpublished |
ContentSaveEvents | A content item or page is saved or deleted |
ContentWorkflowEvents | Content is requested for approval, approved or declined |
Signature: Task<Webhook> SaveWebhookAsync(string instanceGuid, Webhook webhook)
Set SecureDeliveryEnabled = true to have Agility sign every delivery with the webhook's signing secret, following the Standard Webhooks spec. Your endpoint then checks the signature with a Standard Webhooks library. See Verifying Signed Webhooks for how.
The first save with secure delivery turned on creates the signing secret (whsec_...):
SigningSecretJustCreated set to true and contains the secret in full. Store it then.SigningSecret you send on a save is ignored.Secure delivery is off by default, so a webhook stays unsigned until you turn it on.
By default, Agility tries each delivery once. To retry failed deliveries, set these properties on the webhook:
webhook.RetriesEnabled = true;
webhook.RetryCount = 4; // retries after the first attempt: 1 to 8
webhook.RetrySpeed = "standard"; // "fast", "standard" or "slow"
| Property | What it does |
|---|---|
RetriesEnabled | Retry failed deliveries. Default false. |
RetryCount | How many retries follow the first attempt, from 1 to 8. |
RetrySpeed | How quickly retries back off: "fast" (from about 30 seconds), "standard" (about 5 minutes) or "slow" (about 30 minutes). Delays grow exponentially, with jitter, up to 24 hours. |
Then save the webhook, as shown under Update a webhook below. For what counts as a failed delivery, see Webhooks.
Webhook webhook = await client.Webhooks.GetWebhookAsync(guid, webhookId);
Console.WriteLine($"{webhook.Name} -> {webhook.Url} (enabled: {webhook.Enabled})");
Signature: Task<Webhook> GetWebhookAsync(string instanceGuid, string webhookId)
GetWebhooksAsync returns one page of webhooks and a continuation token. Pass the token back to get the next page. It's null after the last page.
string? token = null;
do
{
WebhookContinuationListing page = await client.Webhooks.GetWebhooksAsync(guid, take: 50, continuationToken: token);
foreach (var hook in page.Items ?? [])
{
Console.WriteLine($"{hook.RowKey}: {hook.Name}");
}
token = page.Token;
}
while (token is not null);
take is the number of webhooks per page. If you leave it null, the API returns 20.
Signature: Task<WebhookContinuationListing> GetWebhooksAsync(string instanceGuid, int? take = null, string? continuationToken = null)
To change a webhook, get it, change it, and save it back. Keep the RowKey so the save updates the webhook instead of creating a new one.
Webhook webhook = await client.Webhooks.GetWebhookAsync(guid, webhookId);
webhook.ContentSaveEvents = true;
webhook.RetriesEnabled = true;
webhook.RetryCount = 4;
webhook.RetrySpeed = "standard";
Webhook updated = await client.Webhooks.SaveWebhookAsync(guid, webhook);
⚠️ Start from the saved webhook. The on/off settings (
Enabled, the event settings,SecureDeliveryEnabled,RetriesEnabled) are always sent. AWebhookyou build from scratch with only aRowKeyand one change sendsfalsefor the rest, which turns them off.
If a secret is exposed, rotate it. RotateSigningSecretAsync creates a new secret and returns the webhook with it.
Webhook rotated = await client.Webhooks.RotateSigningSecretAsync(guid, webhook.RowKey!);
StoreSecret(rotated.SigningSecret!);
For 24 hours afterwards, deliveries are signed with both the new secret and the previous one (PreviousSigningSecret), so you can deploy the new secret to your endpoint without dropping deliveries. SecretRolledUtc says when the secret was last rotated. Rotating a secret needs Full Control permission.
Signature: Task<Webhook> RotateSigningSecretAsync(string instanceGuid, string webhookId)
GetWebhookHistoryAsync lists a webhook's deliveries, newest first, a page at a time. Page through it with the continuation token, as with GetWebhooksAsync.
string? token = null;
do
{
WebhookHistoryContinuationListing page = await client.Webhooks.GetWebhookHistoryAsync(guid, webhookId,
fromDate: DateTime.UtcNow.AddDays(-1), take: 100, continuationToken: token);
foreach (var delivery in page.Items ?? [])
{
Console.WriteLine($"{delivery.QueuedDate:u} {delivery.HttpResponseCode} attempt {delivery.AttemptCount} {delivery.LastError}");
}
token = page.Token;
}
while (token is not null);
| Parameter | What it does |
|---|---|
fromDate | Start of the date range. Default: 7 days before toDate. |
toDate | End of the date range. Default: today. The range can span at most 366 days. |
take | Deliveries per page, at most 100. Default: 20. |
continuationToken | The token from the previous page. |
Each delivery is a WebhookHistory. Useful properties:
| Property | What it holds |
|---|---|
RowKey | The delivery ID. It's the value your endpoint received in the webhook-id header. |
QueuedDate, LastAttemptDate | When the delivery was queued, and when it was last attempted |
SendDate | When an attempt succeeded. Set only after a 2xx response. |
HttpResponseCode | The HTTP status of the last attempt. 0 means the request itself failed, for example a timeout. |
Success | Whether the delivery succeeded |
AttemptCount | How many attempts were made. 1 is the first attempt. |
NextAttemptUtc | When the next retry is due, if one is scheduled |
LastError | A short description of the last failure |
Payload, ResponseText | The body that was sent, and your endpoint's response |
Signed, SignatureKeyCount | Whether the last attempt was signed, and with how many signatures (2 during the 24 hours after a secret rotation) |
For how long history is kept, see Verifying Signed Webhooks.
Signature: Task<WebhookHistoryContinuationListing> GetWebhookHistoryAsync(string instanceGuid, string webhookId, DateTime? fromDate = null, DateTime? toDate = null, int? take = null, string? continuationToken = null)
await client.Webhooks.DeleteWebhookAsync(guid, webhook.RowKey!);
The method returns nothing. If the delete fails, it throws an AgilityManagementException.
Signature: Task DeleteWebhookAsync(string instanceGuid, string webhookId)
Every error from the API or the network throws an AgilityManagementException. It carries the HTTP status code, the API's own message and a request ID.
try
{
await client.Webhooks.SaveWebhookAsync(guid, webhook);
}
catch (AgilityManagementException ex)
{
Console.Error.WriteLine($"{ex.StatusCode}: {ex.ApiMessage} (request {ex.RequestId})");
}
Reads are retried automatically on temporary failures. Saves, rotations and deletes are never retried. See the Intro for the full list of exceptions and the retry rules.