# Notification Delivery Service

{% hint style="success" %}
Added in version 2.4.
{% endhint %}

The Notification Delivery Service enables developers to write applications that respond to access or changes to data and permissions in wallets. Applications can subscribe to the following notification types:

* **`AccessRequestPending`** - An agent has created an [Access Request](https://docs.inrupt.com/reference/glossary#access-request) and is awaiting a response
* **`AccessRequestDenied`** - An Access Request was denied
* **`AccessGrantIssued`** - An [Access Grant](https://docs.inrupt.com/reference/glossary#access-grant) was issued and is now available for use
* **`AccessGrantRevoked`** - An Access Grant was revoked and is no longer available for use
* **`AccessGrantExpired`** - An Access Grant has expired
* **`ResourceCreated`** - Triggered when a resource is created
* **`ResourceUpdated`** - Triggered when a resource is modified
* **`ResourceDeleted`** - Triggered when a resource is deleted
* **`ContainerCreated`** - Triggered when a container is created
* **`ContainerUpdated`** - Triggered when a container is modified
* **`ContainerDeleted`** - Triggered when a container is deleted

The service delivers messages to a webhook URL provided by a subscription. This enables application developers to connect notifications to end-user apps securely, reliably, and in a timely manner. The [Verifying Webhook Requests](#verifying-webhook-requests) section describes the options for authenticating requests at the user-supplied webhook URLs.

There are two parts to an end-to-end solution that uses the Notification Delivery Service:

1. An external HTTP endpoint (webhook): The Notification Delivery Service delivers messages to this endpoint. The section below describes the shape of the content sent to the webhook and the options for authenticating the requests.
2. A subscription configuration: The section below describes the API for defining and managing subscriptions and retrieving failed message deliveries.

This service provides two sets of endpoints. The first set, beginning with the path **`/subscriptions/*`**, is available to authenticated users for creating and managing subscriptions. Notifications will be sent for these subscriptions only when the agent has authorization to access the resource that has been accessed or changed. For example, when an Access Grant is created that targets a specific agent, a notification will be delivered if that agent has subscribed to Access Grant creation notifications. In contrast, that agent will not be notified about Access Grants created for any other agents.

The second set of endpoints, beginning with the path **`/system/subscriptions/*`**, is available to system managers for creating and managing subscriptions where the legal basis for processing data is by contract. An example may be when a user accepts a set of terms and conditions to use a service. These subscriptions have a broader scope and allow system managers to create subscriptions that can, for example, notify an endpoint about every Access Grant created in the system. Only authorized system managers can create these subscriptions, and authorization is based on a configured allow-list, as described below.

## Getting started with webhooks

Webhooks are simple HTTP APIs that receive messages from third-party applications. A webhook designed to integrate with the Notification Delivery Service will accept JSON-formatted data and verify its authenticity.

The Notification Delivery Service uses the HTTP POST method to deliver JSON payloads with the following structure:

```json
{
  "id": "97c700d8-901d-4652-ad2d-1470b2694616",
  "subscription": "f17b0d3e-477b-4294-a463-25760fd9176f",
  "published": "2025-02-05T20:56:52Z",
  "type": "AccessGrantIssued",
  "purpose": "Record when Access Grants are issued",
  "controller": "https://id.example/owner",
  "audience": "https://id.example/recipient",
  "resource": "https://credential.example/grant/32649e65-99b7-4265-b727-214dcefbe0f3",
  "dataMinimization": {
    "retentionPeriod": "P30D"
  }
}
```

### Notification Content

Unless stated otherwise, each field described below will be present in every notification event.

* **`id`**: This field uniquely identifies each notification event.
* **`subscription`**: This field uniquely identifies the subscription that produced this notification. An application can manage this subscription at the endpoint: **`https://notification.{domain}/subscription/{subscription}`**. All subscription metadata can be found at the subscription endpoint, including the location of public keys for verifying the message, a URL for fetching delivery failures, and options for deleting the subscription. More information is outlined in the [subscription management](#notification-delivery-service-endpoints) section.
* **`published`**: This field is an [ISO-8601](https://docs.inrupt.com/reference/glossary#iso-8601) timestamp indicating when the Notification Delivery Service created the notification.
* **`type`**: This is the type of event that triggered the notification. Each notification will have one of the supported notification types outlined above.
* **`purpose (optional)`**: If provided when the subscription was set up, this field describes the purpose of the subscription and will not exceed 1024 characters.
* **`controller`**: This field identifies the controller of the resource that changed.
  * In the context of an **`AccessRequest`**, this is the agent that creates the request.
  * In the context of an **`AccessGrant`**, this is the agent that creates the grant.
* **`audience`**: This field identifies the agent to whom the notification is directed.
* **`resource`**: This field identifies the resource associated with this notification.
* **`dataMinimization (optional)`**: This field describes any constraints on the handling of this notification by third parties. If it is present, it will contain the following field:
  * **`retentionPeriod`**: This field describes the ISO-8601 duration that a processing system may retain the data in the notification message.

An OpenAPI schema for Notification Content, called **`NotificationContent`**, is available at **`https://notification.{ESS domain}/openapi`**.

A notification sent to a webhook is always accompanied by HTTP headers that include the signature. The receiving webhook must verify the signature before processing the notification, as described in the section below.

## Verifying Webhook Requests

All messages sent by the Notification Delivery Service will be signed according to [RFC 9421 HTTP Message Signatures](https://www.rfc-editor.org/rfc/rfc9421.html). Applications that receive these messages should verify these signatures before accepting messages. Applications will need to use the public key material published by the Notification Delivery Service during the verification stage. The public key is available at **`https://notification.{ESS domain}/jwks`** and is uniquely identified with the **`kid`** field.

Messages can also, optionally, be secured with mutual TLS. This requires additional configuration when creating the subscription; the details are included in the [Verifying Requests using mTLS](#verifying-requests-using-mtls) section below.

### Verifying Requests using HTTP Message Signatures

There are two steps for verifying a message signature attached to an incoming HTTP request. The first step verifies the integrity of the message body. The second step verifies the integrity of the message headers. Both steps *must* be performed before accepting an incoming request.

### **Verify Message Body**

An application should first verify that the message content can be trusted by extracting the **`Content-Digest`** header from the request. This will be formatted as a structured HTTP field:

```http
Content-Digest: sha-256=:X4XqIRPB13IMNXJsUkkxtpn66eY0v32KT4oDceYubkQ=:
```

This format indicates that the content was hashed with the SHA-256 algorithm. The Base64-encoded digest value appears between the two colons.

To verify the integrity of an incoming message body, an application must generate a digest of the message and compare it to the value in the supplied header. This is an example in Java using the [Apache Commons Codec](https://commons.apache.org/proper/commons-codec/) and [structured-fields](https://github.com/reschke/structured-fields/tree/master) libraries:

```java
byte[] digest = DigestUtils.sha256(inputBody);
Dictionary dictionary = Dictionary.valueOf(
    Map.of("sha-256", ByteSequenceItem.valueOf(digest)));
if (!dictionary.serialize().equals(headerValue)) {
    throw new BadRequestException("Content has been changed");
}
```

If the content matches the provided header exactly, one can move to the next verification stage.

### **Verify Message Headers**

An application can trust that a message body has not been modified only insofar as it can trust the message headers. By verifying an attached signature, the application can know that a trusted entity in possession of a private key produced the message.

To process an HTTP Message Signature, the application needs to build a *Signature Base*, which is an input for the verification process. Creating a *Signature Base* can be performed by parsing the **`Signature-Input`** header. This header is a recipe for creating the *Signature Base* and is necessary for verifying the supplied signature. A **`Signature-Input`** header will have this structure:

```http
Signature-Input: sig=("@method" "@scheme" "@authority" "@path" \
  "content-type" "content-digest"); \
  created=1739474564;expires=1739474864;keyid="identifier"
```

The **`sig`** name will be used to match a signature with the same name in the **`Signature`** header. Typically, there will be only one **`Signature`** and **`Signature-Input`** pair. While this value can be any string, the Notification Delivery Service uses **`sig`**. The elements inside the parentheses indicate the fields and their order when creating a *Signature Base*. The parameters **`created`**, **`expires`**, and **`keyid`** are metadata about the signature: these values can be expected to change over time. If the timestamp represented by **`expires`** has already passed, the signature must be rejected. Signatures generated by the Notification Delivery Service are valid for five minutes.

A receiving application first needs to build an equivalent *Signature Base*, which, for a POST request of JSON data to **`https://webhook.example/api`**, will take the following form:

```json
"@method": POST
"@scheme": https
"@authority": webhook.example
"@path": /api
"content-type": application/json
"content-digest": sha-256=:X4XqIRPB13IMNXJsUkkxtpn66eY0v32KT4oDceYubkQ=:
"@signature-params": ("@method" "@scheme" "@authority" "@path" "content-type" \
  "content-digest");created=1739474564;expires=1739474864;keyid="identifier"
```

Lines are wrapped for display purposes. A *Signature Base* will not include wrapped lines or a trailing newline. The first six lines are defined by the **`Signature-Input`** header. The order and case are important, and the values must be added based on the HTTP operation. The final line (**`"@signature-params"`**) will repeat the data from **`Signature-Input`** but without the **`sig=`** element.

If a header included in the *Signature Base* (e.g. **`Content-Digest`**) is not present in the HTTP request, the signature processor can immediately reject the message.

Once the *Signature Base* has been created, this value must be cryptographically hashed using SHA-256 and then provided to a signature verifier object. The code can now verify the attached signature. That signature will appear in the **`Signature`** header like so:

```http
Signature: sig=:dMT/A/76ehrdBTD/2Xx8QuKV6FoyzEP/I9hdzKN8LQJLNgzU \
  4W767HK05rx1i8meNQQgQPgQp8wq2ive3tV5Ag==:
```

As in the **`Signature-Input`** header, a signature name will identify the signature. The Notification Delivery Service uses the name **`sig`**, although the specification allows any alphanumeric name in that position. The Base64-encoded signature will appear between the colons.

Sample code for verifying a signature in Java is included below:

```java
// Extract the signature from the incoming Signature header
Dictionary signatureHeader = Parser.parseDictionary(req.getHeaderString("Signature"));
ByteSequenceItem signatureField = (ByteSequenceItem) signatureHeader.get().get("sig");
byte[] signature = signatureField.get().array();

// Extract the signature template from the incoming Signature-Input header
Dictionary signatureInputHeader = Parser
    .parseDictionary(req.getHeaderString("Signature-Input"));
InnerList signatureInputField = (InnerList) signatureInputHeader.get().get("sig");
String keyid = (String) signatureInputField.getParams().get("keyid").get();
long expires = (Long) signatureInputField.getParams().get("expires").get();

// Reject the request if the signature has expired
if (expires < Instant.now().getEpochSeconds()) {
    throw new BadRequestException("Invalid signature");
}

// Load the public JSON
PublicKey key = loadPublicJsonWebKey(keyid);

// Create a signature base from the incoming request
byte[] signatureBase = """
    "@method": %s
    "@scheme": %s
    "@authority": %s
    "@path": %s
    "content-type": %s
    "content-digest": %s
    "@signature-params": %s""".formatted(
        req.getMethod(), req.getRequestUri().getScheme(),
        req.getRequestUri().getAuthority(), req.getRequestUri().getPath(),
        req.getHeaderString("content-type"), req.getHeaderString("content-digest"),
        signatureInputField.serialize()).getBytes(UTF_8);

// Create a verifier from the signature base
byte[] hash = DigestUtils.sha256(signatureBase);
Signature verifier = Signature.getInstance("SHA256withECDSA");
verifier.initVerify(key);
verifier.update(hash);

// Verify the request
if (!verifier.verify(signature)) {
    throw new BadRequestException("Invalid signature");
}
```

Special attention should be paid to the **`expires`** parameter. If the timestamp represented by this value has already passed, the signature must be rejected.

A **`PublicKey`** is required for verifying a signature. The Notification Delivery Service publishes the public portion of its signing key at **`https://notification.{ESS domain}/jwks`**. The key will have a **`kid`** value that must match the **`keyid`** value in the **`Signature-Input`** header. It is important to fetch this key from a trusted, canonical location, such as directly from the Notification Delivery Service itself. A verification library can cache this public key.

The ESS infrastructure will periodically rotate the signing key. If a signature verifier receives a signature with a **`keyid`** that it does not recognize, this may indicate that the signing key has been rotated, and it is time to fetch the updated key.

Many different libraries can be used to parse a JSON Web Key. [Jose4J](https://bitbucket.org/b_c/jose4j/wiki/Home) and [Nimbus](https://connect2id.com/products/nimbus-jose-jwt) are two popular options for Java.

The **`signatureBase`** should be generated as described above, based on the incoming HTTP request. Once that value is constructed, it should be hashed with a SHA-256 algorithm and passed to a **`verifier`**.

The **`verifier`** in this example will process signatures created using the **`ES256`** algorithm (**`SHA256withECDSA`**). When using RSA keys with the **`RS256`** algorithm, the Java signature name is **`SHA256withRSA`**.

It is now possible to verify the signature. If the signature matches the expected value, then the incoming message headers can be trusted.

### Verifying Requests using mTLS

When users create subscriptions, they can add [mutual TLS](https://docs.inrupt.com/reference/glossary#mutual-tls) (mTLS) authentication to the outbound messages. mTLS is a process where both the client and server authenticate each other during the TLS handshake.

Below is a sample JSON payload for a subscription:

```json
{
  "type": ["AccessGrantIssued", "AccessGrantRevoked", "AccessGrantExpired"],
  "dispatch": {
    "type": "webhook",
    "uri": "https://webhook.example/api",
    "authentication": {
      "type": "mtls",
      "parameters": {
        "serverCertificate": "<Certificate presented by server>"
      }
    }
  }
}
```

When using **`mtls`** authentication, the following optional parameter can be supplied:

* **`serverCertificate (optional)`**: A PEM-formatted certificate string. This is the server’s public certificate, which the webhook server presents to the client during a TLS handshake. The client uses the certificate to verify the server’s identity. The certificate contains the server’s public key and details about the server’s identity and issuer. This parameter is not necessary if the webhook server is secured with a certificate signed by a publicly trusted certificate authority.

User-supplied Certificates must use PEM formatting. Other formats such as DER, PKCS12, or JKS are not supported.

The **`serverCertificate`** parameter, if present, is dynamically appended to ephemeral, request-bound keystores before sending requests to the corresponding webhook URL.

When configuring the trust store for the webhook server, an operator should add the certificate trust chain from the **`https://notification.{ESS domain}/jwks`** key. This trust chain includes the signed root CA certificate for an ESS deployment. When the signing keys are rotated, the root CA certificate will remain constant; as long as it is present in the webhook trust store, key rotations will occur without interruption for mTLS message sessions.

## Notification Delivery Service Endpoints

By default, the Notification Delivery Service is made available at the following root URL:

```
https://notification.{ESS domain}
```

{% tabs %}
{% tab title="User Subscription" %}
An authorized user can create and manage subscriptions through a RESTful API provided by the Notification Delivery Service.

Authentication errors will produce a **`401 Unauthorized`** response. This will include missing, expired or otherwise invalid bearer tokens.

Authorization errors will produce a **`403 Forbidden`** response. This will include cases where the agent has a valid access token but does not have access to the requested resource.

**List subscriptions**

<table data-header-hidden><thead><tr><th width="152.0546875"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint fetches a paged list of subscriptions.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/subscriptions</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token.</td></tr><tr><td>Query Parameters:</td><td><p><strong><code>page</code></strong>: The page number. This must be a positive integer</p><p><strong><code>pageSize</code></strong>: The total number of items included in a single page. This value must be a positive integer less than or equal to 100. The default value is 10.</p></td></tr><tr><td>Response Headers:</td><td><p><strong><code>Link: &#x3C;next-page-url>; rel="next"</code></strong>: A link header pointing to the next page of results. If no additional pages are available, this header will not appear.</p><p><strong><code>Link: &#x3C;prev-page-url>; rel="prev"</code></strong>: A link header pointing to the previous page of results. If a client is viewing the first page of results, this header will not appear.</p></td></tr><tr><td>Example Response:</td><td><pre class="language-json"><code class="lang-json">{
  "items": [
    {
      "id": "a4a91f6f-f379-47cd-9506-f89a4c8e6047",
      "type": [ "AccessGrantExpired", "AccessGrantRevoked", "AccessGrantIssued" ],
      "purpose": "Watch for Access Grant status changes",
      "status": "Active",
      "deliveryFailures":
              "/subscriptions/a4a91f6f-f379-47cd-9506-f89a4c8e6047/delivery-failures",
      "jku": "/jwks",
      "dispatch": {
        "type": "webhook",
        "uri": "https://webhook.example/api/grants"
      }
    },
    {
      "id": "b871c304-fd2b-448e-9efd-c6bbe5091b0f",
      "type": [ "AccessRequestPending" ],
      "purpose": "Watch for Access Requests",
      "status": "Active",
      "deliveryFailures":
              "/subscriptions/b871c304-fd2b-448e-9efd-c6bbe5091b0f/delivery-failures",
      "jku": "/jwks",
      "dispatch": {
        "type": "webhook",
        "uri": "https://webhook.example/api/requests"
      }
    }
  ]
}
</code></pre></td></tr></tbody></table>

**Create a new subscription**

<table data-header-hidden><thead><tr><th width="153.69921875"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint creates a new subscription and requires a JSON body with the subscription definition.</td></tr><tr><td>Method:</td><td><strong><code>POST</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/subscriptions</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token.</td></tr><tr><td>Request Body:</td><td><p><strong><code>type</code></strong>: This field accepts an array of notification types. The supported types are listed above.</p><p><strong><code>purpose (optional)</code></strong>: This field describes the purpose of the subscription. This field is limited to 1024 character strings.<br></p><p><strong><code>dispatch</code></strong>: This field is required and contains the following elements:</p><ol><li><strong><code>type</code></strong>: This field must be <strong><code>webhook</code></strong>.</li><li><strong><code>uri</code></strong>: This field identifies the address of the remote webhook.</li><li><strong><code>authentication (optional)</code></strong>: It is described in the <a href="#verifying-requests-using-mtls">Verifying Requests using mTLS</a> section.</li></ol><p><strong><code>dataMinimization (optional)</code></strong>: This field describes any constraints on the handling of this message by third parties. If it is present, it must contain the following field:</p><ol><li><strong><code>AccessGrantExpired</code></strong> - An Access Grant has expired: this field describes the length of time the data in the notification message may be retained by a processing system. This field supports a subset of ISO-8601 duration format values. In particular, agents may supply day (D), hour (H) and minute (M) values. For example: <strong><code>P30D</code></strong> indicates a 30 day retention period. <strong><code>PT2H30M</code></strong> indicates a two hour and thirty minute retention window.</li></ol></td></tr><tr><td>Response:</td><td>A successful operation will return a <strong><code>201 Status</code></strong> with a JSON response body containing the newly created subscription.</td></tr><tr><td>Example Request:</td><td><pre class="language-json"><code class="lang-json">{
  "type": ["AccessGrantPending", "AccessGrantIssued"],
  "purpose": "Notifications for MyApp.com",
  "dispatch": {
    "type": "webhook",
    "uri": "https://webhook.example/api"
  },
  "dataMinimization": {
    "retentionPeriod": "P30D"
  }
}
</code></pre></td></tr><tr><td>Error Responses</td><td><p>If a client supplies a JSON body that violates data constraints, a <strong><code>400</code></strong> <strong><code>Bad Request</code></strong> error response will be returned. Below is an example of a response to a client request that includes a <strong><code>purpose</code></strong> field that exceeds 1024 characters and also omits the required <strong><code>type</code></strong> field:</p><pre class="language-json"><code class="lang-json">{
  "status": 400,
  "title": "Bad Request",
  "instance": "/subscriptions",
  "violations": [{
    "field": "type",
    "in": "body",
    "message": "must not be null"
  },
  {
    "field": "purpose",
    "in": "body",
    "message": "size must be between 0 and 1024"
  }]
}
</code></pre><p>If a client supplies a JSON body with type values that cannot be parsed into the expected form, a <strong><code>400 Bad Request</code></strong> error response will be returned. Below is an example of a response to a client request that includes a <strong><code>dataMinimization.retentionPeriod</code></strong> field that cannot be parsed into an ISO-8601 duration.</p><pre class="language-json"><code class="lang-json">{
  "status": 400,
  "title": "Bad Request",
  "detail": "Unable to convert 'two days' to an ISO-8601 duration. Please use values such as 'P30D'",
  "instance": "/subscriptions",
  "field": "dataMinimization.retentionPeriod"
}
</code></pre><p>If a client attempts to create more subscriptions than allowed by their quota, a <strong><code>400 Bad Request</code></strong> error response will be returned:</p><pre class="language-json"><code class="lang-json">{
  "status": 400,
  "title": "Bad Request",
  "detail": "Maximum subscription quota met",
  "instance": "/subscriptions"
}
</code></pre></td></tr></tbody></table>

**Fetch a subscription**

<table data-header-hidden><thead><tr><th width="198.71484375"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint fetches an individual subscription. The <strong><code>{subscription}</code></strong> portion of the URL is the subscription id that is included in results from either a <strong><code>POST</code></strong> or <strong><code>GET</code></strong> request to the <strong><code>subscriptions</code></strong> endpoint.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/subscriptions/{subscription}</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token.</td></tr><tr><td>Response:</td><td>A successful operation will produce a <strong><code>200 OK</code></strong> with a JSON response body containing the subscription resource.</td></tr><tr><td>Example Response:</td><td><pre class="language-json"><code class="lang-json">{
  "id": "b871c304-fd2b-448e-9efd-c6bbe5091b0f",
  "type": [ "AccessRequestPending" ],
  "purpose": "Watch for Access Requests",
  "status": "Active",
  "deliveryFailures":
          "/subscriptions/b871c304-fd2b-448e-9efd-c6bbe5091b0f/delivery-failures",
  "jku": "/jwks",
  "dispatch": {
    "type": "webhook",
    "uri": "https://webhook.example/api/requests"
  }
}
</code></pre></td></tr></tbody></table>

**Delete a subscription**

<table data-header-hidden><thead><tr><th width="169.33026123046875"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint deletes an individual subscription. The <strong><code>{subscription}</code></strong> portion of the URL is the subscription id that is included in results from either a <strong><code>POST</code></strong> or <strong><code>GET</code></strong> request to the <strong><code>subscriptions</code></strong> endpoint.</td></tr><tr><td>Method:</td><td><strong><code>DELETE</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/subscriptions/{subscription}</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token</td></tr><tr><td>Response:</td><td>A successful operation will produce <strong><code>204 No Content</code></strong> with an empty response body.</td></tr></tbody></table>

**List subscription delivery failures**

<table data-header-hidden><thead><tr><th width="182.51214599609375"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint fetches a paged list of notification delivery failures for a particular subscription. Items are listed in reverse chronological order, from most recent to oldest. Each item includes the undelivered notification and the status code returned by the remote server.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/subscriptions/{subscription}/delivery-failures</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token</td></tr><tr><td>Query Parameters:</td><td><p><strong><code>page</code></strong>: The page number. This must be a positive integer</p><p><strong><code>pageSize</code></strong>: The total number of items included in a single page. This value must be a positive integer less than or equal to 100. The default value is 10.</p></td></tr><tr><td>Response Headers:</td><td><p><strong><code>Link: &#x3C;next-page-url>; rel="next"</code></strong>: A link header pointing to the next page of results. If no additional pages are available, this header will not appear.</p><p><strong><code>Link: &#x3C;prev-page-url>; rel="prev"</code></strong>: A link header pointing to the previous page of results. If a client is viewing the first page of results, this header will not appear.</p></td></tr><tr><td>Response:</td><td>A successful operation will return a <strong><code>200 OK</code></strong> with a response body containing a list of zero or more delivery failures.</td></tr></tbody></table>

**Example**

A page of delivery failures can contain multiple objects in the **`items`** field, up to the value specified in the **`pageSize`** query parameter or the default **`pageSize`** if the query parameter was not provided. This example contains only a single item.

```json
{
  "items": [
    {
      "id": "ab264ea5-18b7-4f89-9281-1daddf3d6e93",
      "date": "2025-02-18T00:24:05",
      "request": {
        "id": "b51829b7-c8b0-4d81-9ec9-842d222df297",
        "type": "AccessRequestPending",
        "audience": "https://id.example/owner",
        "controller": "https://id.example/creator",
        "resource": "https://credential.example/069ebfd0-1cc1-4c38-b00d-1351bdc61511",
        "subscription": "8f52a455-f856-4c81-af67-3de64ba50a91",
        "purpose": "Listen for Access Requests",
        "published": "2025-02-18T00:23:20"
      },
      "response": "404: Not Found"
    }
  ]
}
```

{% endtab %}

{% tab title="System Subscriptions" %}
An authorized agent can create and manage system subscriptions through a RESTful API provided by the Notification Delivery Service. Authorization to interact with system subscriptions is granted via a set of static authorization rules on the authorization serice such that (assuming `authorization name` is a valid static authorization name, e.g. `SYSTEMSUBSCRIPTIONS`):

1. The agent exists in the configuration `INRUPT_AUTHORIZATION_STATIC_<authorization name>_AGENT_ALLOW_LIST`
2. The client the agent is using exists in the configuration `INRUPT_AUTHORIZATION_STATIC_<authorization name>_CLIENT_ALLOW_LIST`
3. The identity provider that issued the OIDC token for the agent exists in the configuration `INRUPT_AUTHORIZATION_STATIC_<authorization name>_ISSUER_ALLOW_LIST`
4. Access modes are set appropriately with the configuration `INRUPT_AUTHORIZATION_STATIC_<authorization name>_AGENT_ALLOW_LIST` (`C` must be included for agents creating system subscriptions, `R` for agents reading system subscriptions, and `D` for agents deleting system subscriptions).
5. The appropriate resources are set with the configuration `INRUPT_AUTHORIZATION_STATIC_<authorization name>_RESOURCES`. Typically, the expected value is "<https://notification./system/subscriptions,https://notification./system/subscriptions/reprocess,https://notification./system/subscriptions/.+>".

**List system subscriptions**

<table data-header-hidden><thead><tr><th width="160"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint behaves exactly the same as <strong><code>GET</code></strong> <strong><code>/subscriptions</code></strong> except that it fetches system subscriptions.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.</td></tr></tbody></table>

**Create a new system subscription**

<table data-header-hidden><thead><tr><th width="160"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint behaves exactly the same as <strong><code>POST</code></strong> <strong><code>/subscriptions</code></strong> except that it creates system subscriptions.</td></tr><tr><td>Method:</td><td><strong><code>POST</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.</td></tr></tbody></table>

**Fetch a system subscription**

<table data-header-hidden><thead><tr><th width="160"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint behaves exactly the same as <strong><code>GET</code></strong> <strong><code>/subscriptions/{subscription}</code></strong> except that it fetches an individual system subscription.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions/{subscription}</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.token.</td></tr></tbody></table>

**Delete a system subscription**

<table data-header-hidden><thead><tr><th width="160"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint behaves exactly the same as <strong><code>DELETE</code></strong> <strong><code>/subscriptions/{subscription}</code></strong> except that it deletes an individual system subscription.</td></tr><tr><td>Method:</td><td><strong><code>DELETE</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions/{subscription}</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.</td></tr></tbody></table>

**List system subscription delivery failures**

<table data-header-hidden><thead><tr><th width="160"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint behaves exactly the same as <strong><code>GET</code></strong> <strong><code>/subscriptions/{subscription}/delivery-failures</code></strong> except that it lists delivery failures from a system subscription.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions/{subscription}/delivery-failures</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.</td></tr></tbody></table>

<table data-header-hidden><thead><tr><th width="160"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint will initiate an attempt to redeliver any failed messages for the given system subscription. Any new failures will produce new delivery-failure records. This operation is only available for system subscriptions.</td></tr><tr><td>Method:</td><td><strong><code>POST</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions/{id}/delivery-failures/reprocess</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.</td></tr><tr><td>Request Body</td><td><strong>action</strong>: a single value. Currently, retry is supported, which will attempt to re-deliver all failed notifications.</td></tr></tbody></table>

**Fetch system subscription reprocessing status**

<table data-header-hidden><thead><tr><th width="170"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint will retrieve the status of any active reprocessing operation. This operation is only available for system subscriptions.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/system/subscriptions/{id}/delivery-failures/reprocess/{operation}</code></strong></td></tr><tr><td>Authentication:</td><td>Requires a valid Solid OIDC access token and a corresponding entry in the authorization configuration.</td></tr><tr><td>Example Response:</td><td><pre class="language-json"><code class="lang-json">{
  "id": "a1b2c3d4-e5f6-47g8-h9i0-j1k2l3m4n5o6",
  "status": "Active",
  "startedAt": "2025-04-10T12:00:00Z",
  "lastUpdatedAt": "2025-04-10T12:20:00Z",
  "subscription": "f8e7d6c5-b4a3-9876-5432-1fedcba98765",
  "agent": "https://id.example/alice",
  "action": "Retry"
}
</code></pre></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

### **Fetch the signing key**

<table data-header-hidden><thead><tr><th width="177"></th><th></th></tr></thead><tbody><tr><td>Description:</td><td>This endpoint lists the public portion of the key used to sign outbound messages. The response is formatted as a JSON Web Key Set. A key is identified with a <strong><code>kid</code></strong> (key id) field. The associated certificate chain is included in the <strong><code>x5c</code></strong> field.</td></tr><tr><td>Method:</td><td><strong><code>GET</code></strong></td></tr><tr><td>Endpoint:</td><td><strong><code>/jwks</code></strong></td></tr><tr><td>Authentication:</td><td>None required - this endpoint is public</td></tr><tr><td>Example Response:</td><td><pre class="language-json"><code class="lang-json">{
  "keys": [
    {
      "kty": "EC",
      "kid": "5ZW7PRXCylLNsHGzj1BwDA",
      "use": "sig",
      "alg": "ES256",
      "x": "1epBeHhDoyllwfCts_fNOfapzmeGxZ3V75-zAMIjgAY",
      "y": "nVFs4XVp8wvsPmvnfb_s1-YdT43ZD3_3WSMGenlvAYM",
      "crv": "P-256",
      "x5c": ["{X.509 Certificate chain}", "{X.509 Certificate chain}"]
    }
  ]
}
</code></pre></td></tr></tbody></table>

### OpenAPI Annotations

The Notification Delivery Service includes an [OpenAPI](https://www.openapis.org/) description of the various endpoints and schemas. This document is available at **`https://notification.{ESS domain}/openapi`**.

## Hierarchical Resource Matching

{% hint style="success" %}
Added in version 2.5.
{% endhint %}

When you subscribe to notifications for a container, you will automatically receive notifications for all resources within that container. This hierarchical matching is especially useful for applications that need to monitor entire directories of resources without creating individual subscriptions.

For example, if you subscribe to **`ResourceCreated`**, **`ResourceUpdated`**, **`ResourceDeleted`**, **`ContainerCreated`**, **`ContainerUpdated`**, and **`ContainerDeleted`** changes for the container **`https://storage.example.com/container/`,** you will receive notifications under various conditions as follows:

| Action                                                         | Resource Events                                | Container Events                                                                    |
| -------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------- |
| The container itself is created                                | No event                                       | **`ContainerCreated`** on the container                                             |
| A resource directly within the container is created            | **`ResourceCreated`** on the specific resource | **`ContainerUpdated`** on the container                                             |
| A resource in a subdirectory is created                        | **`ResourceCreated`** on the specific resource | **`ContainerCreated`** on the subdirectory, **`ContainerUpdated`** on the container |
| A resource in a subdirectory is updated                        | **`ResourceUpdated`** on the specific resource | No event                                                                            |
| The container is updated (its triples are modified)            | No event                                       | **`ContainerUpdated`** on the container                                             |
| A subdirectory container is updated (its triples are modified) | No event                                       | **`ContainerUpdated`** on the specific subdirectory container                       |
| A resource in a subdirectory is deleted                        | **`ResourceDeleted`** on the specific resource | **`ContainerUpdated`** on the container                                             |
| A subdirectory is deleted                                      | No event                                       | **`ContainerDeleted`** on the subdirectory, **`ContainerUpdated`** on the container |

To use this feature, create a subscription with a container URI (ending with ‘/’) as the resource.

## Resource Subscriptions

{% hint style="success" %}
Added in version 2.5.
{% endhint %}

The service supports subscribing to changes in specific resources. When creating a resource subscription, you must:

1. Specify the resource URI in the **`storage`** field
2. Include at least one resource-related event type (such as **`ResourceCreated`**)
3. Have appropriate authorization to access the resource, either through ownership or because you have been issued an Access Grant with Read access

### Storage Field Requirements

**Important Note**: The **`storage`** field is only required for resource-based subscriptions and only for user-created subscriptions:

* **Resource-based subscriptions**: Any subscription that includes at least one of these event types:
  * **`ResourceCreated`**
  * **`ResourceUpdated`**
  * **`ResourceDeleted`**
  * **`ContainerCreated`**
  * **`ContainerUpdated`**
  * **`ContainerDeleted`**
* **System subscriptions**: Created through the system subscription endpoint, but do not require the **`storage`** field, even for resource-based events.
* **Non-resource subscriptions**: Subscriptions that only include non-resource event types (like **`AccessRequest`**) do not require the **`storage`** field.

### Example Subscription Scenarios

1. **User-created resource subscription** (requires **`storage`** field):

   ```json
   {
     "type": ["ResourceCreated", "ResourceUpdated"],
     "purpose": "Monitor specific resource changes",
     "storage": "https://storage.example.com/container/resource.ttl",
     "dispatch": {
       "type": "webhook",
       "uri": "https://app.example.com/webhook"
     }
   }
   ```
2. **System resource subscription** (does not require **`storage`** field):

   ```json
   {
     "type": ["ResourceCreated", "ResourceUpdated"],
     "purpose": "System-wide monitoring",
     "dispatch": {
       "type": "webhook",
       "uri": "https://system.example.com/webhook"
     }
   }
   ```
3. **Non-resource subscription** (does not require **`storage`** field):

   ```json
   {
     "type": ["AccessRequest"],
     "purpose": "Monitor access requests",
     "dispatch": {
       "type": "webhook",
       "uri": "https://app.example.com/webhook"
     }
   }
   ```

Resource subscriptions can be authorized through:

* Direct resource ownership
* Access Grants providing access to the resource

## Supported Authentication Methods

### UMA Support

{% hint style="success" %}
Added in version 2.5.
{% endhint %}

You can use UMA (User-Managed Access) tokens to authenticate and authorize subscription creation and management. This enables:

1. Resource owners to delegate subscription management to trusted applications
2. Applications to create subscriptions on behalf of users with proper authorization
3. Fine-grained control over subscription permissions

**When UMA Tokens Are Required:** - When creating subscriptions for resources you don’t own or directly control - When an application needs to create subscriptions on behalf of a user - When you’ve been granted access to a resource through Access Grants

When UMA-based authentication is used, the service validates that:

* The token is issued by the configured authorization server
* The token’s audience matches the notification service
* The token has the necessary permissions for the requested operation

For unauthorized requests, the service generates UMA tickets that can be used to obtain authorization.

### Mixed Subscription Authorization

{% hint style="success" %}
Added in version 2.5.
{% endhint %}

The service handles different authorization rules for system subscriptions versus user subscriptions:

* **System subscriptions**: Created by system managers, with broader scope allowing notifications of a certain type about all resources
* **User subscriptions**: Created by individual users, requiring specific authorization to receive notifications about particular resources

When a resource change occurs, the system efficiently finds all matching subscriptions based on:

1. The event type (e.g., **`ResourceCreated`**)
2. The resource URI, including hierarchical matching for containers
3. The subscription category (system or user)
4. The active status of the subscription

This approach enables fine-grained control over who receives notifications while maintaining scalability for system-wide monitoring needs. The subscription discovery logic identifies all authorized subscribers for any given resource event.

For user subscriptions, authorization is enforced at multiple levels: - During subscription creation to verify the user has access to the specified resource - When delivering notifications to ensure the user still has access to the resource - Continuously monitoring credential validity to maintain subscription effectiveness

#### **Subscription Status and Credential Management:**

A subscription’s effectiveness depends on the ongoing validity of the authorization credentials used to create it. When credentials expire or are revoked, the subscription automatically becomes inactive:

* **Credential Expiration**: When the access token or Access Grant used to authorize a user subscription expires, the subscription status changes to inactive and no notifications are delivered until re-authorized with valid credentials.
* **Credential Revocation**: If the Access Grant or authorization credential is revoked, the subscription immediately becomes inactive and stops receiving notifications.
* **Re-authorization**: To reactivate a subscription after credential expiration or revocation, it must be deleted and recreated with new, valid authorization credentials.

For system subscriptions, the authorization is based on the configured system manager allow-lists as described in the Configuration section.

## Configuration

As part of the installation process, Inrupt provides base [Kustomize overlays](https://docs.inrupt.com/ess/2.5/installation) and associated files that require deployment-specific configuration inputs.

The following configuration options are available when updating the inputs for your deployment. The Inrupt-provided base Kustomize overlays may use updated configuration values that differ from the default values.

### Optional

#### **INRUPT\_NOTIFICATION\_DISPATCH\_RETRY\_LIMIT**

*Default*: **`10`**.

This value controls the number of times the Notification Delivery Service will retry failed deliveries to external webhook URLs. Retry attempts include a progressively growing delay to account for transient failures. The message will be placed in a failed delivery queue if every retry attempt fails.

#### **INRUPT\_NOTIFICATION\_FAILED\_DELIVERY\_MAX\_SIZE**

*Default*: **`1000`**.

This value controls the number of failed delivery messages retained for each active subscription. If a subscription has accumulated the maximum number of failed delivery messages, the oldest messages will be removed as new delivery failures occur.

#### **INRUPT\_AUTHORIZATION\_NOTIFICATION\_SYSTEM\_AGENT\_ALLOW\_LIST**

*Default*: **empty**.

This value controls the agents that can manage system subscriptions. If empty, no agent will be authorized to manage system subscriptions.

#### **INRUPT\_AUTHORIZATION\_NOTIFICATION\_SYSTEM\_CLIENT\_ALLOW\_LIST**

*Default*: **empty**.

This value controls the clients that can be used to manage system subscriptions. If empty, no client will be authorized to manage system subscriptions.

#### **INRUPT\_AUTHORIZATION\_NOTIFICATION\_SYSTEM\_ISSUER\_ALLOW\_LIST**

*Default*: **empty**.

This value controls the trusted issuers that can create the access tokens used by clients when managing system subscriptions. If empty, access tokens will not be accepted from any issuer.

#### **INRUPT\_NOTIFICATION\_SUBSCRIPTIONS\_USER\_MAX**

*Default*: **`100`**.

This value controls the maximum number of subscriptions a user can create. There is a hard-coded maximum of 256.

#### **INRUPT\_NOTIFICATION\_SUBSCRIPTIONS\_SYSTEM\_MAX**

*Default*: **`100`**.

This value controls the maximum number of system subscriptions that can be created. There is a hard-coded maximum of 256.

### Configuration Logging

#### **INRUPT\_LOGGING\_CONFIGURATION\_PREFIX\_ALLOW**

*Default*: **inrupt,smallrye.jwt.sign.key.location**

A comma-separated list of configuration property prefixes (**case-sensitive**) that determine which configurations are logged:

* If the list is empty, **NO** configuration property is logged.
* If a configuration property starts with a listed prefix (**case-sensitive**), the configuration property and its value are logged **unless** the configuration also matches a prefix in [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny) (which acts as a filter on [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow) list).

As such, if the configuration matches prefix in both [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow) and [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny), the [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny) takes precedence and the configuration is not logged. For example, if **`inrupt.`** is an allow prefix, but **`inrupt.kafka.`** is a deny prefix, all configurations that start with **`inrupt.kafka.`** are excluded from the logs.

When specifying the prefixes, you can specify the prefixes using one of two formats:

* using dot notation (e.g., **`inrupt.foobar.`**), or
* using the [MicroProfile Config environmental variables conversion value](https://quarkus.io/guides/config-reference#environment-variables) (e.g., **`INRUPT_FOOBAR_`**).

{% hint style="danger" %}
**Warning**

Use the same format for **both** [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow) and [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny).

For example, if you change the format of [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow), change the format of [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny) as well.
{% endhint %}

{% hint style="info" %}
**Tip**

To avoid allowing more than desired configurations, specify as much of the prefix as possible. If the prefix specifies the complete prefix term, include the term delineator. For example:

* If using dot-notation, if you want to match configuration properties of the form **`foobar....`**, specify **`foobar.`** (including the dot **.**) instead of, for example, **`foo`** or **`foobar`**.
* If using converted form, if you want to match configuration properties of the form **`FOOBAR_...`**, specify **`FOOBAR_`** (including the underscore **\_**) instead of, for example, **`FOO`** or **`FOOBAR`**.
  {% endhint %}

#### **INRUPT\_LOGGING\_CONFIGURATION\_PREFIX\_DENY**

*Default*:

A comma-separated list of configuration name prefixes (**case-sensitive**) that determines which configurations (that would otherwise match the [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow)) are not logged. That is, [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny) acts as a filter on [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow). For example:

* If **`foobar.`** is an allowed prefix, to suppress **`foobar.private.`**, you can specify **`foobar.private.`** to the deny list.
* If **`foobar.`** is **not** an allowed prefix, no property starting with **`foobar.`** is logged. As such, you do not need to specify **`foobar.private`** to the deny list.

When specifying the prefixes, you can specify the prefixes using one of two formats:

* using dot notation (e.g., **`inrupt.foobar.`**), or
* using the MicroProfile Config environmental variables conversion value (e.g., **`INRUPT_FOOBAR_`**).

{% hint style="danger" %}
**Warning**

Use the same format for **both** [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow) and [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny).

For example, if you change the format of [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_ALLOW`**](#inrupt_logging_configuration_prefix_allow), change the format of [**`INRUPT_LOGGING_CONFIGURATION_PREFIX_DENY`**](#inrupt_logging_configuration_prefix_deny) as well.
{% endhint %}

### Logging Redaction

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_ACTION**

*Default*: **`REPLACE`**

Type of the redaction to perform. Supported values are:

<table><thead><tr><th width="148.39984130859375">Action</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>REPLACE</code></strong></td><td><em>Default.</em> Replaces the matching text with a specified replacement.</td></tr><tr><td><strong><code>PLAIN</code></strong></td><td>Leaves the matching field unprocessed. Only available if the redaction target is a field (i.e., <strong><code>INRUPT_LOGGING_REDACTION_{NAME}_FIELD</code></strong>).</td></tr><tr><td><strong><code>DROP</code></strong></td><td>Suppresses the matching field. Only available if the redaction target is a field (i.e., <strong><code>INRUPT_LOGGING_REDACTION_{NAME}_FIELD</code></strong>).</td></tr><tr><td><strong><code>PRIORITIZE</code></strong></td><td>Changes the log level of the matching message.</td></tr><tr><td><strong><code>SHA256</code></strong></td><td>Replaces the matching text with its hash.</td></tr></tbody></table>

* If the action is **`REPLACE`** (*default*), see also **`INRUPT_LOGGING_REDACTION_{NAME}_REPLACEMENT`**.
* If the action is to **`PRIORITIZE`**, see also **`INRUPT_LOGGING_REDACTION_{NAME}_LEVEL`**.

For more information on log redaction, see [Logging Redaction](https://docs.inrupt.com/ess/2.5/administration/logging/logging-redaction).

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_ENABLED**

*Default*: **`true`**

A boolean that determines whether the redaction configurations with the specified **`INRUPT_LOGGING_REDACTION_{NAME}_`** prefix is enabled.

For more information on log redaction, see [Logging Redaction](https://docs.inrupt.com/ess/2.5/administration/logging/logging-redaction).

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_EXCEPTION**

Fully qualified name of the exception class to match in the log messages (includes inner exception). Configure to target an exception message class.

For more information on log redaction, see [Logging Redaction](https://docs.inrupt.com/ess/2.5/administration/logging/logging-redaction).

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_FIELD**

Exact name of the field to match in the log messages. Configure to target a specific log message field for redaction.

For more information on log redaction, see [Logging Redaction](https://docs.inrupt.com/ess/2.5/administration/logging/logging-redaction).

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_LEVEL**

A new log level to use for the log message if the **`INRUPT_LOGGING_REDACTION_{NAME}_ACTION`** is **`PRIORITIZE`**.

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_PATTERN**

A regex (see [Java regex pattern](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html#sum)) to match in the log messages. Configure to target log message text that matches a specified pattern.

For more information on log redaction, see [Logging Redaction](https://docs.inrupt.com/ess/2.5/administration/logging/logging-redaction).

#### **INRUPT\_LOGGING\_REDACTION\_NAME\_REPLACEMENT**

Replacement text to use if the **`INRUPT_LOGGING_REDACTION_{NAME}_ACTION`** is **`REPLACE`**.

If unspecified, defaults to **`[REDACTED]`**.

For more information on log redaction, see [Logging Redaction](https://docs.inrupt.com/ess/2.5/administration/logging/logging-redaction).

### Application-Defined Metadata Propagation

#### **INRUPT\_AUDIT\_PRODUCER\_REQUEST\_METADATA\_ALLOW**

A comma-separated list of application-defined properties that can be included in the associated audit events (unless specified in the corresponding [**`INRUPT_AUDIT_PRODUCER_REQUEST_METADATA_DENY`**](#inrupt_audit_producer_request_metadata_deny)).

This configuration is **case-sensitive** to the propagated properties in the baggage.

{% hint style="info" %}
**Tip**

To include a propagated property that was added via the [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow) configuration, ensure that the cases of these properties match.
{% endhint %}

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_AUDIT\_PRODUCER\_REQUEST\_METADATA\_DENY**

A comma-separated list of application-defined properties to exclude from the associated audit messages. This setting takes precedence over [**`INRUPT_AUDIT_PRODUCER_REQUEST_METADATA_ALLOW`**](#inrupt_audit_producer_request_metadata_allow).

This configuration is **case-sensitive** to the propagated properties in the baggage.

{% hint style="info" %}
**Tip**

To include a propagated property that was added via the [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow) configuration, ensure that the cases of these properties match.
{% endhint %}

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_LOGGING\_REQUEST\_METADATA\_ALLOW**

A comma-separated list of application-defined properties that can be included in the associated log messages (unless specified in the corresponding [**`INRUPT_LOGGING_REQUEST_METADATA_DENY`**](#inrupt_logging_request_metadata_deny)).

This configuration is **case-sensitive** to the propagated properties in the baggage.

{% hint style="info" %}
**Tip**

To include a propagated property that was added via the [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow) configuration, ensure that the cases of these properties match.
{% endhint %}

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_LOGGING\_REQUEST\_METADATA\_DENY**

A comma-separated list of application-defined properties to exclude from the associated log messages. This setting takes precedence over [**`INRUPT_LOGGING_REQUEST_METADATA_ALLOW`**](#inrupt_logging_request_metadata_allow).

This configuration is **case-sensitive** to the propagated properties in the baggage.

{% hint style="info" %}
**Tip**

To include a propagated property that was added via the [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow) configuration, ensure that the cases of these properties match.
{% endhint %}

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW**

A comma-separated list of non-baggage request headers to add to the baggage (unless specified in the corresponding [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_DENY`**](#inrupt_request_metadata_propagator_header_deny)); i.e., include these non-baggage request headers as application-defined properties.

The configuration is case-insensitive; i.e., the listed headers do **not** need to match the case of the client request headers. For example, a list that includes **`x-correlation-id`** can match **`x-correlation-id`** header, **`X-CoRrElAtIoN-Id`** header, etc.

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_DENY**

A comma-separated list of non-baggage request headers to exclude from being added to the baggage; i.e., excludes these headers as application-defined properties. This setting takes precedence over [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow).

The configuration is case-insensitive; i.e., the listed headers do **not** need to match the case of the client request headers. For example, a list that includes **`x-correlation-id`** can match (and exclude) **`x-correlation-id`** header, **`X-CoRrElAtIoN-Id`** header, etc.

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_OVERRIDE**

A flag that determines ESS behavior when metadata property is defined both as a header and as a baggage entry:

* If **true**, ESS updates/overrides the baggage entry with the header value.
* If **false** (the default), ESS keeps the baggage entry.

For details, [Duplicate Property Definition](about:blank/administration/application-defined-metadata/#duplicate-property-definition).

#### **INRUPT\_REQUEST\_METADATA\_REFLECTOR\_HEADER\_ALLOW**

A comma-separated list of application-defined properties that can return as response headers (unless specified in the corresponding [**`INRUPT_REQUEST_METADATA_REFLECTOR_HEADER_DENY`**](#inrupt_request_metadata_reflector_header_deny)).

This configuration is **case-sensitive** to the propagated properties in the baggage.

{% hint style="info" %}
**Tip**

To include a propagated property that was added via the [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow) configuration, ensure that the cases of these properties match.
{% endhint %}

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

#### **INRUPT\_REQUEST\_METADATA\_REFLECTOR\_HEADER\_DENY**

A comma-separated list of application-defined properties to exclude from returning as response headers. This setting takes precedence over [**`INRUPT_REQUEST_METADATA_REFLECTOR_HEADER_ALLOW`**](#inrupt_request_metadata_reflector_header_allow).

This configuration is **case-sensitive** to the propagated properties in the baggage.

{% hint style="info" %}
**Tip**

To include a propagated property that was added via the [**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`**](#inrupt_request_metadata_propagator_header_allow) configuration, ensure that the cases of these properties match.
{% endhint %}

See:

* [Manage Application-Defined Metadata Propagation](https://docs.inrupt.com/ess/2.5/installation/customize-configurations/customization-logging/manage-app-defined-metadata) to configure.
* [Application-Defined Metadata](https://docs.inrupt.com/ess/2.5/administration/application-defined-metadata) for more information.

## Additional Information

See also [Quarkus Configuration Options](https://quarkus.io/guides/all-config)
