> This page is for Contentful SDKs.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful-staging.ferndocs.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful-staging.ferndocs.com/developers/docs/_mcp/server.

# Webhooks, snapshots and roles with .NET

> This article details how to create and manage webhooks, snapshots and roles using the .NET client library.

The Content Management API (CMA) is a restful API for managing content in your Contentful spaces. You can use it to create, update, delete and retrieve content using well-known HTTP verbs.

To make development easier for our users, we publish client libraries for various languages which make the task easier. This article details how to use the [.NET client library](https://github.com/contentful/contentful.net) to create, update and delete webhooks, snapshots and roles.

## Pre-requisites

This tutorial assumes you understand the basic Contentful data model as described in the [developer center](/concepts/data-model) and that you have
already read and understand the [getting started tutorial for the .NET client library](/sdks/net/tutorials/getting-started-with-contentful-and-net) and the [using the management API with Contentful and .NET client library](/sdks/net/tutorials/using-the-management-api-with-contentful-and-net) article.

Contentful.net is built on .NET Core and targets .NET Standard 2.0. The client library is cross-platform and runs on Linux, macOS, and Windows.

## Working with webhooks

To learn more about what webhooks refer to [the webhooks concepts article](/extensibility/webhooks/filters).

To create a webhook for a space use the `CreateWebhook` method.

```csharp
var webhook = new Webhook();
webhook.Name = "New product"; // The name of the webhook
webhook.Url = "https://www.example.com/"; // The url to call when the specified event occurrs
webhook.HttpBasicPassword = "Pass"; // Optional basic auth password
webhook.HttpBasicUsername = "User"; // Optional basic auth username
webhook.Headers = new List<KeyValuePair<string, string>>(); // You can pass custom headers with every call
webhook.Headers.Add(new KeyValuePair<string, string>("custom", "header")); // Add a header by the name of "custom" with a value of "header"
webhook.Topics = new List<string>() // Topics are the events that trigger this webhook.
{
    "Entry.*"
};

await client.CreateWebhook(webhook);
```

The topics available are described in more detail in [the webhooks concepts article](/extensibility/webhooks/filters), but the summary is that you specify a `Type` and an `Action`. `Type` can be any of `Entry`, `Asset` or `ContentType` and `Action` can be any of `create`, `save`, `auto_save`, `archive`, `unarchive`, `publish`, `unpublish` or `delete`. `_` is a wildcard and `_.\*` is valid
and would mean to call this webhook for all actions across all types.

Once you have created a webhook you can fetch them from the client library using the `GetWebhooksCollection` and `GetWebhook` methods.

```csharp
var allWebhooks = await client.GetWebhooksCollection();

var webhook = await client.GetWebhook("<webhook_id>");
```

To retrieve more information about a specific webhook call use the `GetWebhookCallDetailsCollection` to retrieve a list of calls made to the webhook or the `GetWebhookCallDetails` to get details of a specific call.

```csharp
var calldetails = await client.GetWebhookCallDetailsCollection("<webhook_id>");

var calldetail = await client.GetWebhookCallDetails("<call_details_id>", "webhook_id");
```

A method is available that gives a more general overview of the total number of webhook calls for a specific webhook and whether they returned a success code.

```csharp
var webhookHealth = await client.GetWebhookHealth("<webhook_id>");

var total = webhookHealth.TotalCalls; // 27
var healthy = webhookHealth.TotalHealthy; // 23
```

To delete a webhook you are no longer using.

```csharp
await client.DeleteWebhook("<webhook_id>");
```

## Roles and memberships

The .NET client library allows for the creation of custom roles. It's complex to understand the permissions and policies system, but the examples below should give you a good overview. For more detailed information, refer to [the management API documentation](/references/content-management-api/overview).

### Creating a role

Start by creating a new `Role` and adding the permissions and policies you need.

```csharp
var role = new Role();
role.Name = "Name of the role";
role.Description = "Describe the role";
role.Permissions = new ContentfulPermissions(); // Add permissions to the role
role.Permissions.ContentDelivery = new List<string>() { "read" }; // What permissions should the role have to the ContentDelivery API
role.Permissions.ContentModel = new List<string>() { "read" }; // What permissions should the role have to the content model, i.e. creating content types and fields
role.Permissions.Settings = new List<string>() { "manage" }; // What permissions should the role have to other type of settings

role.Policies = new List<Policy>(); // Add more granular policies to the role.
role.Policies.Add(new Policy() // Every policy consists of a number of actions and constraints.
{
    Effect = "allow",
    Actions = new List<string>()
    {
        "read",
        "create",
        "update"
    },
    Constraint = new AndConstraint()
    {
        new EqualsConstraint()
        {
            Property = "sys.type",
            ValueToEqual = "Entry"
        }
    }
});
```

This example above would give the role permissions to read entries, assets, and content types, but not edit, create or delete them. It would also give permissions to manage settings for the space. The policies give this role-specific access to read, create and update entries.

### Policies explained

The policies can look daunting and the framework behind them is complex. However, they do make it possible to create granular and flexible authorization rules.

The first property is the `Effect` which is how this policy is to be treated, will it `allow` or `deny` access.

`Actions` represents what actions this policy allows or denies. This is a list of strings but only a certain set of values are acceptable. These are `read`, `create`, `update`, `delete`, `publish`, `unpublish`, `archive`, `unarchive` or `all`.

`Constraint` is an `IConstraint`, which is explained in more detail below.

### Constraints explained

Constraints represent which resources a specific policy applies to. There are five different kinds of constraints.

`AndConstraint` and `OrConstraint` are lists of other `IConstraint`. The `OrConstraint` ensures that at least one of the contained constraints is true and the `AndConstraint` requires all contained constraints to be true.

The `NotConstraint` contains another `IConstraint` and inverts the value of that constraint.

The `EqualsConstraint` contains a `Property` which is a field on the content type or asset and a `ValueToEqual` which contains the value that this field must equal for this constraint to evaluate to be fulfilled.

The `PathConstraint` contains a `Fields` property that gives a path that must exist on the content type. An example is `"fields.heading"` which would only match content types that have the `heading` field present.

Putting them all together looks something like the following.

```csharp
var constraint = new AndConstraint() // AndConstraint is a list of other IConstraints, ensuring all other contained constraints are met
{
    new EqualsConstraint() // This equals constraint constraints this policy to only affect resources with a sys.type of Entry
    {
        Property = "sys.type",
        ValueToEqual = "Entry"
    },
    new PathConstraint(){ //This path constraint constraints this policy to only affect the name field of the resource
        Fields = "fields.name.%"
    }
}
```

Once you've created a `Role` and added the appropriate permissions and policies, you can call the `CreateRole` method.

```csharp
var createdRole = await client.CreateRole(role);
```

You can update, delete and fetch roles with the appropriate methods.

```csharp
var role = await client.GetRole("<role_id>");

var allRoles = await client.GetAllRoles();

var updatedRole = await client.UpdateRole(role);

await client.DeleteRole("<role_id>");
```

## Working with snapshots

A snapshot of an entry is automatically created each time an entry is published and is the state of every field of the entry at that given time. You can use the snapshots of an entry to keep a full version history of the entry content. For more information read [the snapshots documentation](/references/content-management-api/overview).

To get all snapshots for an entry use the `GetAllSnapshotsForEntry`. To get a specific snapshot use the `SingleSnapshot` method.

```csharp
var singleSnapshot = await client.SingleSnapshot("<snapshot_id>", "<entry_id>");

var allSnapshots = await client.GetAllSnapshotsForEntry("<entry_id>");
```

Similarly, a snapshot of a content type is created each time a content type is published and can be fetched using the `GetAllSnapshotsForContentType` and `GetSnapshotForContentType` methods.

```csharp
var singleSnapshot = await client.GetSnapshotForContentType("<snapshot_id>", "<content_type_id>");

var allSnapshots = await client.GetAllSnapshotsForContentType("<content_type_id>");
```

## Working with space memberships

A space membership represents the membership type a user has in a space and you can assign additional roles to a user, flag a user as admin or remove a user from a space.

To get all memberships of a space call the `GetSpaceMemberships` method. To get a single membership call `GetSpaceMembership` method.

```csharp
var singleMembership = await client.GetSpaceMembership("<membership_id>");

var roles = singleMembership.Roles; // A List<string> of all role ids this membership has
var userId = singleMembership.User.SystemProperties.Id; // The id of the user tied to this membership
var isAdmin = singleMembership.Admin; // Whether this membership is the administrator of the space

var allMemberships = await client.GetSpaceMemberships();
```

To update an existing membership use the `UpdateSpaceMembership` method.

```csharp
var singleMembership = await client.GetSpaceMembership("<membership_id>");
singleMembership.Admin = false; // This membership will no longer be administrator of the space

await client.UpdateSpaceMembership(singleMembership);
```

To create a new membership use the `CreateSpaceMembership` method.

```csharp
var newMembership = new SpaceMembership();

newMembership.Roles = new List<string>() {
    "<role_id>"
};

newMembership.Admin = true;

newMembership.User = new User() { // The user must already exist. It cannot be automatically created through the membership.
    SystemProperties = new SystemProperties() {
        Id = "<user_id>",
        LinkType = "User",
        Type = "Link"
    }
};

await client.CreateSpaceMembership(newMembership);
```

To delete a membership use the `DeleteSpaceMembership` method.

> **Info**
>
> It's possible to delete every single membership of a space, leaving no administrator available. You can fix this by inviting a new user through the Contentful web app organization settings.

```csharp
await client.DeleteSpaceMembership("<membership_id>");
```

## Working with API keys

The Contentful .NET client library allows you to get all API keys for the Content Delivery API ((not the management API) for a space. It also allows you to create new API keys.

```csharp
var allApiKeys = await client.GetAllApiKeys();

var accessToken = allApiKeys.First().AccessToken;

var newKey = await client.CreateApiKey("Name of key", "Description of key");

var newAccessToken = newKey.AccessToken;
```

## Working with environments

> **Info**
>
> This is currently a beta feature and only available in the pre-release of the client library and for whitelisted organizations.

To create an environment call the `CreateOrUpdateEnvironment` method.

```csharp
var environment = await client.CreateOrUpdateEnvironment("<environment_id>", "<environment_name>");
```

This environment will be an identical copy to your current master environment. Note that it can take a while for the environment to be fully available depending on the size of the master environment.

To get a list of available environments use the `GetEnvironments` method.

```csharp
var environments = await client.GetEnvironments();
```

To get a specific environment use the `GetEnvironment` method.

```csharp
var environment = await client.GetEnvironment("<environment_id>");
```

To delete an environment use the `DeleteEnvironment` method.

```csharp
await client.DeleteEnvironment("<environment_id>");
```

## Next steps

* [Explore the .NET client library GitHub repository](https://github.com/contentful/contentful.net)