> For the complete documentation index, see [llms.txt](https://docs.veza.com/4yItIzMvkpAvMVFAamTf/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.veza.com/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/custom-rest-payload.md).

# Custom REST Payload

Send a customer-defined REST request to your own endpoint when a reviewer rejects and signs off a row, to trigger remediation in the downstream application.

Custom REST payload actions for Access Reviews enable REST API calls with customizable payloads to specific endpoints, triggered when a reviewer rejects and signs off on a row in a review. Typically, you use these to trigger API-based remediation actions in applications where Veza does not natively deprovision access, or to open an IT service ticket in a system that expects its own request format.

Create a custom REST payload action once in Access Reviews settings and attach it to as many review configurations as needed, as the action to execute on row rejection. On rejected row sign-off, Veza sends one request per rejected row in the background. The outcome of each request is recorded against the row that produced it.

{% hint style="info" %}
**Early Access:** Custom REST payload actions are not enabled by default. Contact Veza support to enable them for your tenant.
{% endhint %}

This capability differs from a [webhook Veza Action](/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/veza-actions.md), which sends a fixed payload to a single URL and does not record what the destination returned. Use a custom REST payload action when the destination endpoint expects a specific request payload, needs custom headers, or returns a value you want recorded with the rejected row in the review.

## How it works

1. An administrator creates a named custom REST payload action, defining the endpoint, method, headers, and body template, and selecting the shared credential that authenticates the request. Authentication is by reference to a stored credential, so the action holds no authentication settings of its own.
2. A review configuration enables that action on reject events.
3. A reviewer rejects rows and signs off.
4. Veza queues the REST action and, for each rejected row, substitutes that row's values and metadata into the URL and body templates to send one request. The request leaves from the Veza control plane, or from an Insight Point if the action selects one.
5. Veza records the outcome against the row, with the response body on success.

Because actions are stored once and referenced by reviews that use them, editing an action changes behavior for every review attached to it. An action cannot be deleted if currently in use by one or more review configurations.

## Requirements

* The custom REST payload action early access feature is enabled for your tenant.
* Your Veza role includes permission to read and manage custom REST payload actions:
  * The Administrator, Access Reviews Admin, and Operator roles can create and edit them, and can attach them to a review configuration.
  * The Access Reviews Monitor and Re-assigner roles can view the list of actions but cannot create, edit, or attach one.
  * Creating the shared credential an action references requires the Administrator role on the root team.
* The target endpoint is reachable over HTTPS, either from the Veza control plane or from an Insight Point deployed in your network. See [Insight Point routing](#insight-point-routing).

## Create a custom REST payload action

1. Go to **Access Reviews** > **Settings** and open the **Remediation** tab.
2. On the **Custom REST Payload** card, select **New Custom REST Payload**.
3. Complete the form, using the [field reference](#field-reference).
4. Select **Save**.

The action now appears in the **Custom REST Payload** table, which lists its name, URL, method, and whether a review is currently using it.

### Field reference

| Section            | Field                              | Notes                                                                                                                                                                                                                                                         |
| ------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Details            | **Name**                           | Identifier for the action in the review configuration settings. Required, 2–255 characters, and unique within the tenant.                                                                                                                                     |
| Details            | **Description (Optional)**         | Free-text summary of what the action does. Up to 1023 characters.                                                                                                                                                                                             |
| REST Endpoint      | **URL**                            | Destination for the request, which can contain placeholders, such as `https://www.example.com/api/{user_name}`. Must be an `http` or `https` URL, and `https` when a credential is selected. Required.                                                        |
| REST Endpoint      | **HTTP Method**                    | One of `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`. A new action starts with `POST` selected. The API also accepts `HEAD` and `OPTIONS`.                                                                                                                       |
| Insight Point      | **Insight Point**                  | Insight Point that issues the request, instead of the Veza control plane. Optional. Leave it empty to send from the control plane. Required when the endpoint is not reachable from the public internet. See [Insight Point routing](#insight-point-routing). |
| Credentials        | **Auth Credentials**               | Shared REST credential that authenticates the request. Optional; an empty value sends an unauthenticated call.                                                                                                                                                |
| Request Headers    | **Header Name** / **Header Value** | Header pairs, added one at a time with **Add Header**. Values are sent literally and do not support placeholders. Optional.                                                                                                                                   |
| Request Payload    | **JSON Payload**                   | Body template for the request, valid as JSON before substitution. The form shows this field for `POST`, `PUT`, and `PATCH`, and requires a value.                                                                                                             |
| Additional Options | **Timeout in seconds**             | Request timeout, up to 300. `0` uses the default of 60 seconds. Optional.                                                                                                                                                                                     |

{% hint style="info" %}
At send time, the action's own URL and HTTP method take precedence over the credential's.

**Available in Veza v2026.10.5 and later:** when you select a credential that has a saved URL or method, the form populates an empty **URL** field and replaces the preselected **HTTP Method** unless you have already chosen one. A value you entered, or one already saved on the action, is kept. The form shows the credential's values under the fields as **Credentials Saved URL** and **Credentials Saved Method**.

On earlier releases, selecting a credential that has a saved URL or method replaces the **URL** and **HTTP Method** values with the credential's and disables both fields.
{% endhint %}

### Example: open a ServiceNow catalog request on rejection

An access review of Okta users' access to a finance application. When a reviewer rejects a row, a request is submitted in the ServiceNow service catalog for the IAM operations team to remove the access.

Set the **URL** to the `order_now` endpoint of the catalog item that handles access revocations, such as `https://example.service-now.com/api/sn_sc/servicecatalog/items/9f8e7d6c5b4a39281706f5e4d3c2b1a0/order_now`. Set the **HTTP Method** to `POST`, and the **JSON Payload** to a body that mixes templated values with static ones:

```json
{
  "sysparm_quantity": "1",
  "variables": {
    "user_email": "{email}",
    "user_full_name": "{full_name}",
    "application": "{OktaApplication.name}",
    "reason": "Access review rejected this access. Reviewer note: {AccessReviewDecision.notes}",
    "reviewer_email": "{AccessReviewDecision.approver_email}",
    "decided_at": "{AccessReviewDecision.decided_at}",
    "correlation_id": "{id}-{AccessReviewDecision.decided_at}"
  }
}
```

The `sysparm_quantity` value is static and passes through unchanged. The names under `variables` are the variables your own catalog item defines, so use that item's names rather than these. Placeholders are substituted at any depth, so values nested under `variables` behave exactly like top-level ones. The `correlation_id` value gives ServiceNow a stable key it can use to discard a repeated request.

For a rejected row, Veza sends:

```json
{
  "sysparm_quantity": "1",
  "variables": {
    "user_email": "jsmith@example.com",
    "user_full_name": "Jane Smith",
    "application": "SAP Concur",
    "reason": "Access review rejected this access. Reviewer note: Left the Finance team on 2026-08-01",
    "reviewer_email": "dreviewer@example.com",
    "decided_at": "2026-08-18T14:22:03Z",
    "correlation_id": "00u1a2b3c4d5-2026-08-18T14:22:03Z"
  }
}
```

ServiceNow responds with the number of the request it created, and Veza records the response body against the row that produced the request:

```json
{
  "result": {
    "number": "REQ0010042",
    "sys_id": "d4c3b2a1908f7e6d5c4b3a2918070f6e"
  }
}
```

Veza records the raw response as shown above and makes it available through the review results API. See [Response output](#response-output).

Veza stores that body verbatim: it does not parse the response, extract `number` into a row column, or display it in the review interface. Treat ServiceNow as the system of record for the request, and template a stable correlation value into the payload, like `correlation_id` in this example, so that ServiceNow holds the link between the rejected row and the request it opened.

This example orders a catalog item, which is why ServiceNow returns a `REQ` number. The mechanism is not specific to the service catalog: to open an incident, or to write to any other table, change the URL and the JSON payload to the shape that endpoint expects.

Some endpoints instead carry the identity in the URL and take no meaningful body, such as an account-deactivation call sent as `PUT https://sap.example.com/backofficeusers/{name}/disable`. Templating the URL is the whole mechanism there. The form still requires a payload for `PUT`, so send `{}` where the endpoint ignores the body. For a method the form does not show the payload field for, such as `DELETE`, the request is sent without one.

### Attach the action to a review

Open a review configuration and go to the **Veza Actions** step. On the reject event, select your action from the **Custom REST Payload** group.

{% hint style="info" %}
**Reject events only.** Selecting a custom REST payload action on any other event (approve, reassign, complete, edit notes, or mark as fixed) is refused when you save the review configuration.
{% endhint %}

## Authentication

Custom REST payload actions in Access Reviews can share the same authentication credentials as Lifecycle Management Send REST payload actions. Supported authentication types include header, basic, bearer, login-to-bearer, OAuth2 client credentials, and OAuth2 password authentication. To create or edit a credential, see [REST Auth Credentials](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/rest-auth-credentials.md).

Notes:

* The action stores only the credential's ID. It has no field for an inline authorization header, so the secret itself stays in the credentials store. For the credential types that mint a token, Veza fetches the token once per batch of rows rather than once per row.
* Client certificate authentication (mTLS) is not supported.
* A request sent from the control plane is validated against the public truststore, so an endpoint presenting an untrusted or self-signed certificate is refused. To trust a private certificate authority, see Insight Point routing.
* Credentials are managed in **Administration** > **Credentials**, and creating or editing one requires the Administrator role on the root team. The REST credential API does not itself require a Lifecycle Management license.

{% hint style="warning" %}
REST credentials are a tenant-wide shared resource. Credentials created for Access Reviews are visible to, and usable by, other Veza products that send REST requests (Lifecycle Management and Access Requests), and vice versa. Every create, update, and delete is recorded in the audit log.
{% endhint %}

## Placeholder substitution

The URL and the JSON payload can reference values from the rejected row using single curly braces.

Header values are **not** templated. A placeholder in a header value is sent as literal text.

```
URL:     https://example.com/api/users/{name}/disable
Payload: {"caller_id": "{email}", "u_application": "{OktaApplication.name}"}
```

The following groups of values are available.

**Attributes of the reviewed identity**, by bare name — `{email}`, `{department}`, `{employee_id}`, and so on. The available names are whatever attributes that entity type carries in the graph, plus `{id}` and `{type}`. Names are case-insensitive, so `{Email}` and `{email}` behave identically. A bare name resolves against the reviewed identity only, so to reference any other entity in the row, qualify it by entity type as described next.

**Attributes of other entities in the row**, qualified by *entity type* — `{OktaApplication.name}`, `{OktaGroup.name}`. Every entity the review query returns for the row is addressable this way, including the resource, entities joined into the query, and entities along the access path. Where two entities in a row share the same type, an entity-type reference resolves to the last one in the path, so use a role name instead to reach a specific one.

**Attributes of the row's columns**, by *role name* — `{source.name}`, `{destination.name}`, `{waypoint.name}`, and `{path_summary.name}`. These address a node by the column it occupies rather than by what it is: `source` is the reviewed identity, `destination` the resource, `waypoint` an intermediate node, and `path_summary` a node on the access path. Both forms work, so `{source.email}` and `{email}` reach the same value. Role names matter most when two columns hold the same entity type, such as a group-to-group review where an entity-type reference would reach only one of them. Where a row carries several path summary nodes, `{path_summary.name}` resolves to the last one on the path.

**The reviewer's decision**, on a fixed node named `AccessReviewDecision`:

| Placeholder                                 | Value                                                                          |
| ------------------------------------------- | ------------------------------------------------------------------------------ |
| `{AccessReviewDecision.decision}`           | The decision, as an enum name — for a rejected row, `RESULT_DECISION_REJECTED` |
| `{AccessReviewDecision.notes}`              | The reviewer's note on the row                                                 |
| `{AccessReviewDecision.approver_name}`      | Name of the reviewer who made the decision                                     |
| `{AccessReviewDecision.approver_email}`     | Their email address                                                            |
| `{AccessReviewDecision.approver_id}`        | Their Veza user ID                                                             |
| `{AccessReviewDecision.approver_user_type}` | Their user type                                                                |
| `{AccessReviewDecision.decided_at}`         | When the decision was made, in RFC 3339 format                                 |
| `{AccessReviewDecision.signed_off_at}`      | When the row was signed off, in RFC 3339 format                                |

**The review itself**, on a fixed node named `AccessReview`. **Available in Veza v2026.9.28 and later.**

| Placeholder           | Value             |
| --------------------- | ----------------- |
| `{AccessReview.name}` | The review's name |
| `{AccessReview.id}`   | The review's ID   |

**The row itself**, on a fixed node named `AccessReviewResult`. **Available in Veza v2026.10.5-2 and later.**

| Placeholder                      | Value                            |
| -------------------------------- | -------------------------------- |
| `{AccessReviewResult.result_id}` | The row's result ID, as a string |

Unlike `AccessReviewDecision`, this node is present on every row, including rows with no recorded reviewer. Together with `{AccessReview.id}`, it identifies the row in the review results API. A destination that opens a ticket in an IT service management (ITSM) system can use the pair to write the ticket back to the row. See [ITSM request columns](/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/columns.md#itsm-request-columns).

The list of assigned reviewers is not available for substitution.

Beyond simple references, the transformer supports transformations and expressions, such as changing case, combining several values in one field, and conditional values. See [Transformers](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/transformers.md) for the syntax. Two functions are unavailable as this action runs without a graph cache: `FROM_ENTITY_ATTRIBUTE` and `FROM_MANY_ENTITIES_ATTRIBUTE`. Lookup tables are available.

The Lifecycle Management `$target.` prefix does not apply here. It refers to a provisioning destination, which a review row does not have, and the form omits it from placeholder suggestions.

{% hint style="info" %}
Because an action is a reusable definition that any review can attach, the entity types available to it cannot be determined while you are editing it. Write placeholders against the query of the review you intend to attach the action to and check the request that review produces.
{% endhint %}

### Substitution rules

Only values containing a placeholder are substituted. Anything else passes through unchanged. Static fields are written as plain literals, and types are preserved. Nested objects and arrays keep their shape.

A substituted value cannot change the structure of the request. Values are substituted at the leaves of the parsed JSON, so a value stays inside the field it was substituted into.

A value that introduces a control character into the URL, such as a newline or a carriage return, fails validation and Veza does not send the request.

Substituted values are not URL-encoded, so place row values only where an additional path or query segment would be acceptable.

{% hint style="warning" %}
**Unresolved placeholders do not prevent delivery.** If a placeholder names an attribute the row does not have, Veza still sends the request, and that field arrives holding the placeholder text itself, braces included.
{% endhint %}

Two situations produce this. If a single action is attached to several reviews, an entity type present in one review's query may be absent from another's. For rows with no recorded reviewer, every `AccessReviewDecision` placeholder arrives literally.

## Response output

Veza records the outcome of the call against the row the request was sent for. On success, it records the response body, truncated if the body is larger than 4096 bytes, and the record notes that truncation happened. On failure, it records an error message that includes the HTTP status rather than a body.

These records are returned by the review results API, on each row, as `rest_action_infos`: `GET /api/preview/awf/certifications/{certification_id}/results` for a review's rows, or `GET /api/preview/awf/certifications/{certification_id}/results/{result_id}` for one row. The review interface does not display them.

## Delivery and retries

A row is attempted at most three times. Veza retries a row whose request failed for a transient reason: a 5xx response, a 408 or 429, a timeout, or a network or DNS error. Any other 4xx response ends the row on its first attempt, because a refused request produces the same answer when it is sent again.

Credential failures are handled separately and do not consume attempts. If Veza cannot obtain the credentials or a required auth token, it requeues the whole action rather than failing the rejected rows, retrying at about one-minute intervals for up to 30 minutes from when the work was queued. Rows are reported failed only after that window closes.

The outcome of each attempt is recorded against the row. When a call fails, Veza also raises an **Error** event against the review, naming the action, the row, and the error, indicating a rejection did not reach the downstream system.

{% hint style="warning" %}
Delivery is at-least-once, so a retry can repeat the request and therefore repeat its effect in the destination.
{% endhint %}

If a repeated request would cause a problem, such as a duplicate ticket, template a stable value into the URL or the JSON body, such as the reviewed identity's `{id}` and `{AccessReviewDecision.decided_at}`, and use it in the destination to recognize and discard a repeat.

## Insight Point routing

By default, Veza sends the request from the control plane, which reaches only endpoints published to the public internet. To call an endpoint inside your network, select an **Insight Point** on the action. The request is issued from that [Insight Point](/4yItIzMvkpAvMVFAamTf/integrations/connectivity/insight-point.md), from inside your network. The response returns to Veza over the connection the Insight Point already maintains. No inbound firewall rule is required, and the target endpoint does not need a public address or a public DNS record.

The action addresses an Insight Point directly. Unlike the Lifecycle Management [Send REST Payload](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/actions/send-rest-request.md) action, which reaches its Insight Point through a data source's provider, a custom REST payload action needs no integration, no provider, and no data source.

### Routing requirements

Routing requires an Insight Point deployed with connectivity to the target endpoint. The **Insight Point** list offers the Insight Points deployed in your tenant. Veza's own internal data plane is excluded, and selecting it is refused when you save.

If the list is empty, the form reports that no Insight Points were found, and the selector is unavailable until one is deployed.

To return an action to control-plane sending, clear the **Insight Point** selection.

### Certificate trust

Insight Point routing moves certificate validation from the Veza control plane to the Insight Point. When the target API presents a certificate issued by an internal or private certificate authority, add that authority to the Insight Point's truststore.

Otherwise, the request fails with `x509: certificate signed by unknown authority`. See [Custom Certificate Configuration](/4yItIzMvkpAvMVFAamTf/integrations/connectivity/insight-point/ova-v2.md#custom-certificate-configuration) for an OVA (Open Virtual Appliance) deployment, or [Using Custom Certificates](/4yItIzMvkpAvMVFAamTf/integrations/connectivity/insight-point/insight-point-install-script.md#using-custom-certificates) for an install-script deployment.

### Authentication over a routed request

Credentials are selected on the action as usual, and the full exchange stays on one path. For the credential types that fetch a token (login-to-bearer, OAuth2 client credentials, and OAuth2 password), the Insight Point issues both the token request and the REST request, so an authorization server on a private address remains reachable.

Selecting both an Insight Point and a credential adds three rules, checked when you save:

* The action's URL must use the same host and port as the credential's registered URL. A default port counts as a match, so `https://example.com` and `https://example.com:443` are the same target. The host comparison is case-sensitive.
* The credential must have a registered URL. Without one, there is no host to compare.
* An `Authorization` header cannot be combined with a credential. Send one or the other. The header name is matched regardless of case.

A request sent from the control plane is not subject to these rules.

## Limitations

* **Rejections only.** Only the reject event sends a custom REST payload. Any other event is refused when you save the review configuration.
* **A private endpoint or a private certificate authority requires Insight Point routing.** A request sent from the control plane fails for every row when the URL names a loopback, link-local, RFC 1918 private, IPv6 unique-local, or similarly reserved address, and the action saves without reporting it. A certificate outside the public truststore is refused.
* **Redirects are not followed.**
* **The JSON payload must be valid JSON before substitution.** A malformed template is rejected when you save. A misspelled attribute name is not reported at save time and surfaces as literal placeholder text in the resulting request.
* **Shared actions are shared.** Editing an action affects every review that uses it, and an action in use cannot be deleted.

## See also

* [Veza Actions for Access Reviews](/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/veza-actions.md) — the other action types available on review events, and the fixed webhook payload
* [Auto-Revocation](/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/auto-revocation.md) — remove access directly on sign-off, for integrations Veza provisions
* [Verify Remediations](/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/verify-remediations.md) — confirm that rejected access was removed and mark rows as Fixed
* [REST Auth Credentials](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/rest-auth-credentials.md) — create and manage the shared credentials this action uses
* [Transformers](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/transformers.md) — the full substitution and transformation syntax
* [Insight Point](/4yItIzMvkpAvMVFAamTf/integrations/connectivity/insight-point.md) — deploy and manage the Insight Points this action can send from


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.veza.com/4yItIzMvkpAvMVFAamTf/features/access-reviews/configuration/custom-rest-payload.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
