> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contraforce.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Gamebook Actions: Bring Your Own Response

> Add your own response actions to Gamebooks. Run Logic Apps, Power Automate flows, Azure Functions, or internal APIs with the same policies and approvals.

Custom Gamebook actions let you add your own response actions to Gamebooks. Register an HTTPS endpoint, such as an Azure Logic App, a Power Automate flow, an Azure Function, or an internal API, and ContraForce runs it with one click, right next to built-in actions like **Lockout User** and **Isolate Endpoint**.

> [!NOTE]
>
> **Who is this for?** Service providers and security teams who want to extend the response actions their analysts and AI agents can run, and the engineers who build the endpoints behind those actions.

> [!NOTE]
>
> **Availability:** Custom actions are rolling out gradually. If you don't see the **Actions** tab on the **Gamebooks** page, contact your ContraForce representative to enable it for your organization.

***

## A Quick Refresher on Gamebooks

[Gamebooks](https://docs.contraforce.com/guides/getting-started/what-are-gamebooks.md) are ContraForce's no-code incident response workflows. You pick an entity on an incident, such as a user or a device, add response actions to a plan, and run it. ContraForce executes each action against your connected security tools, with no API mapping, coding, or scripting.

Every Gamebook follows the same lifecycle: it's **planned**, checked against your [workspace policies](https://docs.contraforce.com/guides/getting-started/workspace-policies.md), **approved** when a policy or approver requires it, **run**, and **recorded** in the Gamebook history and the incident's comments. Security Delivery Agents can build and propose Gamebooks too.

Out of the box, Gamebooks ship with built-in actions for Microsoft Entra ID, Defender XDR, CrowdStrike, SentinelOne, and more. Custom actions let you add your own.

***

## What Are Custom Gamebook Actions?

A custom action is a response action you define, backed by an HTTPS endpoint you own. When a Gamebook runs it, ContraForce sends your endpoint a signed request that describes the run: the incident, the target with every identifier ContraForce knows for it, the workspace, and who asked for it. Your endpoint does the work and reports back.

A custom action isn't a second-class add-on. It goes through the same planning, policies, approvals, AI agents, live status, run history, and audit log as the actions ContraForce ships.

***

## Why Use Custom Actions?

Every security team has response steps that live outside Microsoft and the EDR: opening a ticket in the HR or ITSM system, disabling an account in an on-premises directory, notifying a business owner, or blocking a sender in a third-party email gateway. Until now, those steps happened in another tool after the Gamebook finished.

Custom actions bring them into the Gamebook. For service providers, that means you can **expand the action catalog** your analysts and agents work from: build an action once, publish it to every workspace you manage, and offer your customers response capabilities beyond the built-in set.

> [card] One place to respond
>
> Analysts run your internal steps next to the built-in ones, in the same Gamebook, against the same incident and entities. No switching tools, no copying IDs by hand.

> [card] Your automation, our guardrails
>
> The same policies decide whether a custom action runs, needs approval, or is blocked. Every run is audited.

> [card] Works with what you already built
>
> If it can receive an HTTPS POST, it can be a custom action: Logic Apps, Power Automate, Azure Functions, AWS Lambda function URLs, ServiceNow or Jira automation webhooks, or your own service.

> [card] AI agents can use it, if you allow it
>
> You decide per action whether Security Delivery Agents may propose it. The description you write tells the agent when it fits.

> [card] Results you can see
>
> Your endpoint can report a message and its own values, such as a ticket number, and they appear on the run, live.

> [card] Long-running work is supported
>
> An endpoint that waits on an approval or a slow system can answer right away and report progress for up to 10 minutes.

***

## Where Custom Actions Apply

### Every workspace or one workspace

Choose where an action lives when you create it.

| Scope | Who creates it | Who can run it |
| - | - | - |
| **For every workspace** | An **Org Admin** of the service provider or parent organization | Every workspace the organization manages inherits it. Workspaces see it but can't change it. |
| **For one workspace** | An **Owner** of that workspace | Only that workspace |

> [!TIP]
>
> Build actions that every customer needs, like "Open a ticket in our SOC ticketing system," once **for every workspace**. Keep customer-specific integrations, like a single customer's HR system, **for one workspace**.

### Any entity, the incident, or every entity at once

Each action has a **Target** that decides what it acts on and where ContraForce offers it.

| Target | Offered on | What your endpoint receives |
| - | - | - |
| **User** | Every user entity | Entra object ID, user principal name, email address |
| **Device** | Every device entity | Entra device ID, Defender device ID, source device ID, host name |
| **IP address** | Every IP address entity | The IP address |
| **File** | Every file entity | File hash, hash algorithm, file name |
| **Email message** | Every email entity | Internet message ID, mailbox object ID |
| **The incident itself** | Every incident, from any entity; runs once | The incident, with no target |
| **Every entity in the incident, at once** | Every incident, from any entity; runs once | Every entity of the incident (up to 100) |

ContraForce sends every identifier it knows for the target, so your endpoint doesn't have to look them up.

***

## Custom Actions vs. Webhooks

At its core, a custom action is a signed HTTPS POST to your endpoint, just like a webhook. The difference is that a webhook only **tells** you something happened. A custom action **is** a response step, and it shares the full Gamebook lifecycle inside ContraForce.

| | Webhook | Custom Gamebook action |
| - | - | - |
| **Trigger** | Fires on an event, such as an incident being created | Runs when an analyst, a Gamebook, or an AI agent chooses it |
| **Appears in the Gamebook planner** | No | Yes, next to the built-in actions for the entity |
| **Workspace policies and approvals** | Not applicable | Allow, block, or ask an approver first, like any action |
| **AI agents** | Not applicable | Agents can propose it, if you allow it |
| **Status** | Delivered or failed | **Running**, then **Finished** or **Failed**, live on the run |
| **Results** | Not shown in ContraForce | Your message and values appear on the run |
| **History** | Webhook delivery log | Gamebook history, incident comments, and the audit log |

### The lifecycle of a custom action run

```mermaid theme={null}
flowchart LR
    A[Planned in a Gamebook] --> B{Policies and approvals}
    B -- Blocked --> X[Not run]
    B -- Allowed or approved --> C[Signed request to your endpoint]
    C --> D[Running]
    D --> E[Finished]
    D --> F[Failed]
    E --> G[Gamebook history, incident comments, audit log]
    F --> G
```

**1. You define the action once**

Give it a name, a description, a target, its endpoint, and how ContraForce authenticates to it.

**2. ContraForce offers it where it fits**

A custom action on users appears next to the built-in user actions on every user entity, in every workspace that can use it.

**3. Gamebooks run it like any other action**

Policies and approvals apply first. Then ContraForce sends your endpoint a signed request describing the run.

**4. Your endpoint reports back**

An immediate endpoint answers within 30 seconds. A long-running endpoint answers at once and reports through an update link for up to 10 minutes.

**5. Everything is recorded**

The run appears in the Gamebook history and the incident's comments, and changes to custom actions are in the audit log.

***

## Prerequisites

| What you want to do | Who can do it |
| - | - |
| See the custom actions a workspace can run | Anyone who can see the workspace's Gamebooks |
| Create, change, test, or delete an action for one workspace | An **Owner** of that workspace |
| Create, change, test, or delete an action for every workspace | An **Org Admin** of your organization |
| Run a custom action | Anyone who can run Gamebooks in the workspace, subject to its policies and approvals |

Before you start, have these ready:

* **An HTTPS endpoint on a public internet address.** Private addresses, `localhost`, and cloud metadata addresses are refused. Redirects aren't followed, so use the final URL.
* **How ContraForce authenticates to it**, if the URL alone isn't enough: a bearer token, a user name and password (basic authentication), or an API key in a header such as `x-functions-key` or `Ocp-Apim-Subscription-Key`. A Logic App or Function URL that already carries its key (`sig=` or `code=`) needs nothing else.
* **Firewall rules**, if your endpoint isn't open to the internet. Ask your ContraForce representative for the outbound addresses for your region.

***

## Set Up a Custom Action

**1. Open the Actions tab**

In the ContraForce portal, open **Gamebooks** and select the **Actions** tab. Under **Custom actions**, you'll see two sections:

* **For every workspace**: the actions every workspace you manage inherits. Only Org Admins can change them.
* **For one workspace**: choose a workspace from the drop-down to see its own actions and the ones it inherits.

![Gamebooks page, Actions tab, listing custom actions for every workspace and for one chosen workspace, each with an Add custom action button](https://mintcdn.com/contraforce/sBOENrkkTo-7fsNr/images/custom-actions/custom-actions-page.png?fit=max\&auto=format\&n=sBOENrkkTo-7fsNr\&q=85\&s=9d298e124dd5f4d9902520f09341dfd7)

Workspace Owners can also manage their workspace's custom actions from the workspace's **Gamebooks** tab, in the **Custom actions** card.

**2. Add the action**

Select **Add custom action** in the section where the action should live. The **Add custom action** panel opens.

![Add custom action panel with Name, Target, Description with placeholder chips, Endpoint URL, Risk, Authentication, and the Long-running toggle](https://mintcdn.com/contraforce/sBOENrkkTo-7fsNr/images/custom-actions/create-action.png?fit=max\&auto=format\&n=sBOENrkkTo-7fsNr\&q=85\&s=f44959a119e47800172cbfd821288a7e)

Fill in the fields:

| Field | What to enter |
| - | - |
| **Name** | What people see when they plan the action, for example `Open an HR ticket`. Up to 120 characters. |
| **Description** | What the action does and when to use it. People and AI agents read this to decide when the action fits. You can insert [placeholders](#description-placeholders). Up to 1,000 characters. |
| **Target** | What the action acts on: a user, a device, an IP address, a file, an email message, the incident itself, or every entity in the incident at once. |
| **Risk** | **Low**, **Medium**, or **High**: how much harm the action does if it runs on the wrong target. Shown next to the action when people plan it. |
| **Endpoint URL** | The HTTPS address ContraForce POSTs to. |
| **Authentication** | **Signature only, or a key in the URL**, **Bearer token**, **Basic authentication**, or **API key header** (with its header name). Every request is also signed with the action's signing secret. |
| **Long-running** | Turn on when your endpoint answers right away and reports the result later through the update link. |
| **Requires approval** | On by default. Every Gamebook with this action waits for one of the workspace's Gamebook approvers. |
| **AI agents can use it** | Lets Security Delivery Agents see the action and its description and plan it. Their Gamebooks still follow the workspace's policies and approvals. |
| **Runs without an incident** | Lets people run the action directly on a user, device, or other target, without an incident. Needs a target. |
| **Enabled** | A turned-off action isn't offered, and Gamebooks that already planned it fail at that action without calling the endpoint. |

**3. Save and copy the signing secret**

Select **Create action**. ContraForce shows the action's **signing secret** once. Copy it and store it with your endpoint, for example in Azure Key Vault or your flow's secure inputs. Your endpoint uses it to verify that a request really came from ContraForce.

> [!WARNING]
>
> The signing secret is shown only once. If you lose it, use **Replace signing secret** on the action. The old secret stops working immediately.

**4. Test the action**

Select **Test** (the send icon) on the action's row. ContraForce sends a signed test request with the header `X-CF-Test: true` and shows whether the endpoint answered, its status code, and how long it took.

A test never runs against a real target. Make sure your endpoint recognizes the test header and does nothing real.

**5. Run it**

Open an incident, choose an entity in the **Investigation graph**, and select your action next to the built-in ones. It's added to the Gamebook plan in the **Response** panel. Select **Run Gamebook**.

![Investigation graph with a user entity menu listing built-in actions and the custom Block User Location action, which is added to the Gamebook plan in the Response panel](https://mintcdn.com/contraforce/sBOENrkkTo-7fsNr/images/custom-actions/use-action.png?fit=max\&auto=format\&n=sBOENrkkTo-7fsNr\&q=85\&s=3451a37be69f4952c3128e9706e92b11)

You should see the action move from **Running** to **Finished** or **Failed**. Select the run to see the message and details your endpoint reported.

### Other ways to run a custom action

* **From the Gamebook planner**: add the action to a Gamebook like any other action.
* **Without an incident**: if **Runs without an incident** is on, run it directly on a user, device, or other target.
* **By an AI agent**: if **AI agents can use it** is on, Security Delivery Agents may propose it when the description fits the incident.

Actions that target the incident itself, or every entity at once, are offered from any entity and run once per Gamebook.

### Description placeholders

Placeholders in the description are filled in for each run, and the filled-in text is sent to your endpoint as `summary`. Click a placeholder chip in the panel to insert it.

| Placeholder | Replaced by |
| - | - |
| `{{target.name}}` | The name of the target, such as the user principal name or the host name |
| `{{target.id}}` | The target's main ID: Entra object ID, Entra device ID, IP address, file hash, or internet message ID |
| `{{target.kind}}` | The kind of target: `User`, `Device`, `IpAddress`, `File`, or `EmailMessage` |
| `{{incident.title}}` | The title of the incident, when the Gamebook is linked to one |
| `{{incident.number}}` | The number of the incident, when the Gamebook is linked to one |
| `{{workspace.name}}` | The name of the workspace the action runs in |
| `{{entities.count}}` | How many entities the action is sent, when it acts on every entity of the incident |
| `{{requestedBy}}` | Who ran the Gamebook, or who approved it |

For example: `Opens a high-priority ticket in our HR system to offboard {{target.name}} for incident {{incident.number}}.`

> [!TIP]
>
> Write the description for the AI agent as much as for people. Say what the action does **and** when it should be used, for example "Use when a user is confirmed compromised and must be offboarded."

***

## Policies and Approvals

Custom actions appear in the [workspace policy](https://docs.contraforce.com/guides/getting-started/workspace-policies.md) builder next to the built-in actions, so a run policy can **allow**, **block**, or **ask an approver first** for them, just like any other action.

**Requires approval** on the action itself always queues the Gamebook for one of the workspace's Gamebook approvers, whatever the policies say. Keep it on for actions that change something outside ContraForce.

> [!WARNING]
>
> Turning on **AI agents can use it** doesn't bypass approvals. Agent Gamebooks still follow the workspace's policies, the action's **Requires approval** setting, and the workspace's **Allow service providers to run gamebooks** setting.

***

## Build Your Endpoint

This section is for the engineer who builds the endpoint behind the action.

### The request

ContraForce sends one `POST` with a JSON body and these headers:

| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `X-CF-Signature` | `sha256=` followed by the lowercase hex HMAC-SHA256 of `{timestamp}.{body}`, keyed with the signing secret |
| `X-CF-Timestamp` | The Unix time, in seconds, the request was signed |
| `X-CF-Event-Id` | The ID of this run |
| `X-CF-Schema` | `contraforce.gamebook.action.requested.v1` |
| `Idempotency-Key` | The ID of this run, so you can recognize a repeated delivery |
| `X-CF-Test` | `true`, on a test request only |
| Your authentication | `Authorization: Bearer ...`, `Authorization: Basic ...`, or your API key header, when configured |

Example body for an action on a user:

```json theme={null}
{
  "specVersion": "1.0",
  "type": "contraforce.gamebook.action.requested.v1",
  "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
  "time": "2026-11-04T09:05:00+00:00",
  "mode": "immediate",
  "action": {
    "id": "custom-6f9619ff8b86d011b42d00c04fc964ff",
    "name": "Open an HR ticket",
    "version": 2
  },
  "workspace": { "id": "11111111-2222-3333-4444-555555555555", "name": "Contoso" },
  "gamebook": {
    "id": "3a8d2f7c-5b1e-4c9a-8d6f-2e0a1b3c4d5e",
    "title": "20261104.3",
    "url": "https://portal.contraforce.com/..."
  },
  "incident": {
    "id": "incident-001",
    "number": 42,
    "title": "Impossible travel",
    "source": "Sentinel"
  },
  "target": {
    "kind": "User",
    "id": "aad-object-id",
    "displayName": "ana@contoso.example",
    "properties": {
      "userObjectId": "aad-object-id",
      "userPrincipalName": "ana@contoso.example",
      "emailAddress": "ana@contoso.example"
    }
  },
  "entities": null,
  "summary": "Opens a high-priority ticket in our HR system to offboard ana@contoso.example for incident 42.",
  "requestedBy": "analyst@contoso.example",
  "callback": null
}
```

| Field | Notes |
| - | - |
| `mode` | `immediate` or `longRunning` |
| `incident` | `null` for a Gamebook without an incident |
| `target` | `null` for an action on the incident itself |
| `target.properties` | Every identifier ContraForce knows for the target. Possible keys: `userObjectId`, `userPrincipalName`, `emailAddress`, `entraDeviceId`, `defenderDeviceId`, `sourceDeviceId`, `hostName`, `ipAddress`, `fileHash`, `fileHashAlgorithm`, `fileName`, `internetMessageId`, and `mailboxObjectId`. Only the known ones are sent. |
| `entities` | Every entity of the incident (up to 100) for an action on every entity; otherwise `null` |
| `summary` | Your description, with its placeholders filled in |
| `callback` | In long-running mode, the update link (`url`) and when it expires (`expiresAt`); otherwise `null` |

The contract is versioned. Fields may be added in version 1 but never renamed or removed, so ignore fields you don't recognize.

### Verify the signature

Always verify the signature before you act, and refuse requests whose timestamp is more than a few minutes old. Sign the **raw body exactly as received**, before any JSON parsing.

> [!WARNING]
>
> This signature format differs from the [agent investigation webhook](https://docs.contraforce.com/guides/agent-center/agent-investigation-webhook.md): here the timestamp is Unix seconds and the signature is `sha256=` plus lowercase hex, not Base64. Don't reuse that verifier unchanged.

```python verify.py theme={null}
import hashlib, hmac, time

def is_from_contraforce(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = "sha256=" + hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

```csharp Verify.cs theme={null}
static bool IsFromContraForce(string secret, string timestamp, string body, string signature)
{
    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp)) > 300)
        return false;
    var hash = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{timestamp}.{body}"));
    var expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature));
}
```

```javascript verify.js theme={null}
const crypto = require("crypto");

function isFromContraForce(secret, timestamp, rawBody, signature) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  return expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```

### Answer an immediate action

Do the work and answer within **30 seconds**.

| Your answer | Result in ContraForce |
| - | - |
| `2xx` with no body | **Finished** |
| `2xx` with a [report](#the-report) | **Finished**, or **Failed** if the report says `"status": "failed"` |
| `3xx` | **Failed**: redirects aren't followed |
| `4xx` (except `408` and `429`) | **Failed**: the endpoint refused the request |
| `5xx`, `408`, `429`, a timeout, or no connection | **Failed**, marked as worth retrying |

### Answer a long-running action

Answer at once, for example with `202 Accepted`. Then `POST` reports to the `callback.url` from the request as the work progresses, and a final `succeeded` or `failed` report when it's done.

The update link:

* Needs no extra authentication. The link itself is the secret, so keep it private and don't log it.
* Works only while the action is running, for at most **10 minutes**.
* Stops working after the first `succeeded` or `failed` report.
* Answers `202` when a report is accepted, and `404` when the link is unknown, expired, or already finished.

If no final report arrives in time, the action fails as timed out.

### The report

An answer or an update can carry a report. Every field is optional.

```json theme={null}
{
  "status": "succeeded",
  "message": "Ticket HR-1234 opened and assigned to the offboarding team.",
  "metadata": {
    "ticket": "HR-1234",
    "queue": "Offboarding"
  }
}
```

| Field | Meaning |
| - | - |
| `status` | `running`, `succeeded`, or `failed` |
| `message` | A short message people see on the run, and the reason when it failed. Up to 1,000 characters. |
| `metadata` | Your own values, shown on the run. Up to 25 entries. Keys use letters, digits, `.`, `_`, and `-` (up to 64 characters); values up to 1,024 characters. A later report updates earlier values; send a key with `null` to remove it. |

A report sent to the update link can be up to 32 KB.

### Example: an Azure Logic App

**1. Create the trigger**

Create a Logic App with the **When an HTTP request is received** trigger. Copy its URL. It already carries its `sig` key, so choose **Signature only, or a key in the URL** as the authentication.

**2. Verify the signature**

Add a step that verifies `X-CF-Signature`, for example an Azure Function or an inline code action that reads the signing secret from Key Vault. Return `401` when it doesn't match.

**3. Handle test requests**

If the request has the `X-CF-Test` header, return `200` and stop.

**4. Do the work**

Read `target.properties.userPrincipalName`, `incident.number`, and `summary` from the body, and create your ticket.

**5. Report the result**

Return `200` with a report: `{ "status": "succeeded", "message": "Ticket created", "metadata": { "ticket": "<id>" } }`.

For a flow that waits on a person, turn on **Long-running**, answer `202` right away, and `POST` the result to `callback.url` when it's done.

***

## Manage Custom Actions

From the action's row on the **Actions** tab, or the workspace's **Custom actions** card:

| Action | Icon | What it does |
| - | - | - |
| **Test** | Send | Sends a signed test request. |
| **Edit** | Pencil | Opens the side panel. Changing what the endpoint receives (its URL, authentication, target, or mode) raises the action's version. |
| **Replace signing secret** | Key | Issues a new secret, shown once. The old one stops working immediately, so update your endpoint first. |
| **Delete** | Trash | Removes the action. Gamebooks that already planned it fail at that action. |

To pause an action without deleting it, edit it and clear **Enabled**. It stays saved but isn't offered.

> [!WARNING]
>
> When you raise an action's version, Gamebooks planned before the change fail at that action without calling the endpoint and need to run again. The panel warns you before you save.

Creating, changing, replacing the secret of, testing, and deleting a custom action are all recorded in the audit log.

***

## Security

* **Signed requests.** Every request carries an HMAC-SHA256 signature and a timestamp, so your endpoint can reject forged and replayed requests.
* **Public HTTPS only.** ContraForce checks every address it connects to, refuses private, loopback, link-local, and cloud metadata addresses, and never follows redirects.
* **Fixed egress addresses.** Requests leave from ContraForce's fixed outbound addresses for your region, so you can allow only those.
* **Secrets stay secret.** The endpoint URL, credential, and signing secret are stored encrypted. The full URL and credential are never shown again after you save; the signing secret is shown only when created or replaced.
* **No shared state between customers.** Requests never carry cookies or another workspace's session state.
* **One run, one call.** A run calls your endpoint once. A retry is a new run with a new `Idempotency-Key`, started by a person.
* **The same controls as built-in actions.** Policies, approvals, role checks, and the audit log apply to every custom action.

***

## Troubleshooting

| What you see on the run | Likely cause | What to do |
| - | - | - |
| `endpoint_unavailable` | The endpoint timed out (30 seconds), couldn't be reached, or answered `5xx`, `408`, or `429` | Check that the endpoint is up and fast enough. For slow work, turn on **Long-running**. Run the Gamebook again. |
| `endpoint_refused` | The endpoint answered `4xx` or a redirect, or its address isn't a public internet address | Check the URL, the authentication, and your signature check. Use **Test** to see the status code. |
| `definition_changed` | The action was changed after the Gamebook was planned | Run the Gamebook again; it uses the current definition. |
| `definition_unavailable` | The action was turned off or deleted | Turn it back on, or plan a different action. |
| Timed out while running | A long-running endpoint didn't report `succeeded` or `failed` within 10 minutes | Make sure your flow POSTs the final report to `callback.url`. |
| The action isn't offered on an entity | The action targets another kind of entity, is turned off, or the workspace can't use it | Check the action's **Target** and **Enabled** settings, and whether it was created for that workspace or for every workspace. |
| No **Actions** tab on the Gamebooks page | Custom actions aren't enabled for your organization yet | Contact your ContraForce representative. |

***

## FAQ

> [accordion] Can one action call different systems per workspace?
>
> An action for every workspace calls the same endpoint for all of them, and the request names the workspace (`workspace.id` and `workspace.name`). Route on that in your endpoint, or create an action per workspace.

> [accordion] Does a custom action need Microsoft consent in the workspace?
>
> No. It calls your endpoint, not Microsoft, so it needs no Microsoft Graph or Defender consent. When ContraForce can't look a user up in Microsoft Entra ID, it still sends the user principal name and email address it has.

> [accordion] Can AI agents run custom actions without a person?
>
> Only if you turn on **AI agents can use it**, and only within the workspace's policies and approvals. Leave **Requires approval** on to have a person approve every run.

> [accordion] What happens if my endpoint receives the same run twice?
>
> ContraForce sends each run once. Use the `Idempotency-Key` header to detect a repeated delivery from your own infrastructure.

> [accordion] Can a custom action patch systems or fix vulnerabilities?
>
> Custom actions are for incident response: containment and notification steps that run during a Gamebook. ContraForce doesn't provide vulnerability remediation, such as patching or configuration hardening.

***

## Related Guides

> [card] [What Are Gamebooks?](https://docs.contraforce.com/guides/getting-started/what-are-gamebooks.md)
>
> Build, run, and approve Gamebooks with the built-in response actions

> [card] [Workspace Policies](https://docs.contraforce.com/guides/getting-started/workspace-policies.md)
>
> Allow, block, or require approval for Gamebook actions, including custom ones

> [card] [Configuring Security Delivery Agents](https://docs.contraforce.com/guides/getting-started/configuring-security-delivery-agents.md)
>
> Control when AI agents build and run Gamebooks

> [card] [Roles and Permissions Reference](https://docs.contraforce.com/guides/general-support/roles-and-permissions-reference.md)
>
> See which roles can manage custom actions and run Gamebooks

***

> [!NOTE]
>
> Questions about custom Gamebook actions? Contact us at [support@contraforce.com](mailto:support@contraforce.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.