Manage and protect
Grok Bot inference hooks
Inference hooks send every governed Grok Bot model request to an AI security server your organization runs or uses through a security vendor. Cursor signs each request, waits for an allow or deny verdict, and stops the request on a deny, so your policy checks prompts and the content Grok Bot gathers before a model sees them. For Grok Bot's other controls, see Grok Bot security.
Beta Inference hooks are in beta and available to all Enterprise teams. Team admins can set them up from the dashboard. They cover Grok Bot only. See Coverage.
How it works
- Each governed model request is checked. Before Grok Bot calls a model,
Cursor sends your endpoint a signed
promptframe with the conversation so far and holds the request until a verdict arrives or the timeout passes. A turn that calls the model several times, for example between tool calls, sends one frame per call. - Your server decides.
allowlets the request go to the model.denystops it, ends the turn, and the member who sent the message sees a block notice. - Failures follow your mode. When your endpoint times out, returns an error, or answers with something other than a verdict, the mode decides whether the request proceeds or is blocked. A failure never counts as a deny.
- Only model inputs are checked. Your endpoint judges what is about to go to the model. A tool call and its result appear in a frame only after the tool has run. Grok Bot's reply to a member is a tool call too, so the reply reaches the member before any frame contains it. A deny stops the next model call, not an action that already happened.
Set up inference hooks
You need to be a team admin on an Enterprise team.
- Prepare your security server. Create an inference hooks integration in your security vendor's console, or host your own server that meets the endpoint requirements. The setup dialog's Security Vendor list includes vendor presets with a setup checklist.
- Open Inference Hooks. In the Cursor dashboard, select the team, then open Inference Hooks from the Security & Compliance section of the Grok Bot page.
- Add the endpoint. Click Set Up Endpoint, choose your vendor or Custom Endpoint, and enter the Endpoint URL and any Custom Headers your server needs, such as its API key. Cursor checks the URL when you save.
- Copy the signing secret. The first save that sets an endpoint generates
a signing secret starting with
whsec_and shows it once. Paste it into your vendor's webhook secret field, or your server's configuration, so it can verify each request. - Test the connection. Click Test Connection. Cursor sends a synthetic request and shows the verdict or the reason the check failed. A deny here means your policy would block the test prompt, so fix that before you enforce.
- Start in Shadow mode. New configurations start in Shadow Mode. Turn on Enforce Verdicts to start sending. Your endpoint receives every covered request and denials are recorded in the audit log, but nothing is blocked. Once the verdicts look right, set Mode under Failure Handling to Allow the Request or Block the Request.
Setting changes take effect within about a minute, and requests already in flight finish under the old settings.
While an endpoint is saved, Enforce Verdicts is on, and Grok Bot is covered, in any mode and at any rollout percentage, Grok Bot skips some model calls it would otherwise make ahead of a turn, so replies can start more slowly.
Endpoint requirements
- Use an
https://URL on port 443. The host must be publicly routable and present a certificate from a public certificate authority. Cursor refuses private, loopback, and carrier-grade NAT addresses. - Redirects and tunnels don't work. Cursor doesn't follow redirects and treats one as a failed check. Reverse-tunnel hosts aren't accepted.
- Keep secrets out of the URL. The URL can't carry credentials, a query string, or a fragment, because admins see it in plain text. Put credentials in custom headers, which are stored encrypted. URLs are limited to 2,048 characters.
- Answer every request with a verdict. Return HTTP 200 and a JSON verdict, including for Test Connection. See Verdicts.
Custom headers
- You can add up to 16 headers. Names use letters, digits, and hyphens, without underscores, up to 128 characters. Values are printable ASCII, up to 8,192 characters.
- Values are write-only. After you save, the page shows header names only. Changing any header means entering every value again.
- A new URL clears saved values. Header values never go to an endpoint they weren't saved for. After you change the URL, re-enter them in Edit. Until you do, requests go out without them.
- Cursor owns some headers. You can't add
Host,Cookie,User-Agent,Accept-Encoding, connection headers such asTransfer-Encoding, client-address headers such asX-Real-IP, or any name starting withContent-,Proxy-,X-Forwarded-,Webhook-, orX-Cursor-.
Signing secret
Every request carries Standard Webhooks headers so your server can reject anything Cursor didn't send:
| Header | Value |
|---|---|
webhook-id | The frame's request_id |
webhook-timestamp | Unix time in seconds |
webhook-signature | v1, then the base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{raw body}, keyed with the base64-decoded part of the secret after whsec_ |
Each request carries exactly one signature. To rotate the secret, click Rotate Secret under Request Signing. The new secret is shown once, and Cursor starts signing with it within about a minute, so keep accepting the previous secret until then. Rotating counts as a settings change for the circuit breaker.
Test Connection
Test Connection sends a signed prompt frame with source.application set to
config-test, a fixed message that contains no user content, and no member,
session, or model. It signs with your saved secret and waits for your saved
verdict timeout. It uses the URL and headers in the form when you run it from
the endpoint dialog, and the saved ones when you run it from the page.
| Result | Meaning |
|---|---|
| Allow or deny | Your server returned a verdict. Resolve a deny before you enforce |
| URL rejected | The URL doesn't meet the endpoint requirements |
| Private or internal IP | The host isn't a publicly routable address |
| Timeout | No verdict arrived within the verdict timeout |
| Transport error | DNS, connection, or TLS failure |
| Non-200 status | Your server answered with a status other than 200, including a redirect |
| Unparseable response | The body wasn't a JSON verdict, was compressed, or was larger than 64 KiB |
| Signing secret required | Save the configuration first. Saving generates the secret |
Modes
Enforce Verdicts turns sending on and off. While it's off, Cursor sends nothing and keeps your settings. While it's on, Mode decides what happens when a check can't finish:
| Mode | On a deny | When the check fails |
|---|---|---|
| Shadow Mode (default) | Lets the request through | Lets the request through |
| Allow the Request | Blocks the request | Lets the request through uninspected (fails open) |
| Block the Request | Blocks the request | Blocks the request (fails closed) |
In Allow and Block modes the model request waits for the verdict. In Shadow mode Cursor sends the same frames at the same moments, but in the background: nothing waits, members see nothing, and denials and failures are recorded in the audit log. Under heavy load, Cursor can skip a Shadow check, and a skipped check writes no audit row.
A check fails when your server times out, answers with a status other than 200,
returns a body that isn't a JSON verdict or is larger than 64 KiB, returns an
action other than allow or deny, or can't be reached. A frame larger than
64 MiB also counts as a failure.
Once Cursor has your settings, Block mode blocks every check that can't finish,
including when the cause is on Cursor's side. If Cursor can't load your team's
inference hook settings at all, it can't tell which mode you chose, so the
request proceeds and the audit log records it as failed open with reason
config_unavailable.
Verdict timeout
Verdict Timeout (ms) accepts 1 to 10,000 ms and defaults to 5,000. It
covers the whole exchange: connection, TLS handshake, request, and response. If
the connection fails, Cursor retries once within the same timeout, with the
same webhook-id and signature, so you can deduplicate on webhook-id. Cursor
never retries after your server responds. Its own processing adds up to about
half a second on top of the timeout.
Rollout
Requests Inspected (%) sends only a share of covered requests to your endpoint while you bring it up. The choice is sticky, so all the requests behind one turn, subagents included, are either inspected or skipped. Requests outside the share proceed uninspected even in Block mode, and 0% turns inspection off.
Circuit breaker
Sustained failures your server is responsible for, such as timeouts, error statuses, TLS errors, and malformed verdicts, trip the circuit breaker. DNS and connection errors, URLs Cursor refuses, and frames over 64 MiB count as failures but never trip it.
- While tripped, Cursor stops sending. Your mode applies to every covered
request: Block mode blocks them all, and Allow and Shadow let them through
uninspected. The audit log records one
inference_hooks_circuit_breaker_trippedevent per trip and no per-request events while tripped. - Recovery is automatic. Starting 10 minutes after the trip, Cursor periodically sends your server the Test Connection request. An allow or a deny resets the breaker.
- Any settings change stops automatic recovery. That includes rotating the secret. Once your endpoint is fixed, click Reset, or turn Enforce Verdicts off and on again.
Cursor pause
Cursor can pause inference hooks for a team, or for everyone, in an emergency.
While paused, Cursor sends no frames, covered requests proceed, and the audit
log records most of them as failed open with reason kill_switch. Test
Connection shows "Inference hooks are temporarily paused by Cursor."
What members see
When your server denies a request, the Grok Bot desktop and mobile apps show a block notice to the member who sent the blocked message, in their own conversation with the Bot. On mobile, the notice sits above the message box. It shows:
- The title Blocked by your organization's policy.
- Your server's
deny_reason, followed by your Blocked Message. - Guidance with an Edit button and a Reset button. Edit takes the blocked message back out of the conversation and puts its text in the message box, unless the member already has a draft there, so they can send something different. Reset clears the conversation. The mobile app and newer versions of the desktop app ask the member to confirm first. When Cursor can't roll the conversation back to the point before the blocked message, only Reset appears.
Blocked Message accepts up to 500 characters and defaults to "Your
organization's security policy blocked this request." Turn it off to show only
your server's deny_reason. Use it to tell members what to do next, such as
who to contact.
After a reset, the Bot no longer sees earlier messages in that conversation, and it stops the background helpers it was running for it. Other work it started, such as cloud agents and background commands, keeps going and can report back. Its memory, files, routines, and settings are kept. Edit rolls the conversation back to the point before the blocked message. Neither one undoes actions the Bot already took, such as files it wrote.
If the conversation is blocked again right after a reset, the notice has no buttons. It explains that what's blocked may be in the Bot's memory or files, and asks the member to remove it there or ask an admin for help.
When a check fails in Block mode, the desktop app shows Your organization's policy check is unavailable and "Your organization requires a policy check before the model responds, and the check didn't complete. Try again later, or contact your admin if this keeps happening." The mobile app shows "Your organization's policy check is unavailable. Try again later, or contact your admin if this keeps happening." Your Blocked Message isn't shown, and there's no Edit or Reset.
Other places show less:
- Shared Bots notify only the sender. In a team Bot, the notice and its buttons go only to the teammate who sent the blocked message, in their own chat with the Bot, and Edit or Reset changes only that chat. The Bot's owner isn't shown the deny reason or your Blocked Message. Rooms and group chats get no Edit or Reset buttons.
- Slack replies in the conversation. The Bot answers "Your organization's policy blocked this conversation, so I can't reply," and in a one-to-one DM it quotes the deny reason and your Blocked Message, which channel threads and group DMs never show. The reply offers Remove blocked message, which works like Edit, and Reset, which asks for confirmation first, or only Reset when Cursor can't roll the conversation back. Anyone who can use the Bot and has linked their Cursor account can click them. A removed message stays visible in Slack, but the Bot no longer sees it. A repeat block right after a reset gets no buttons, and a failed check in Block mode gets the Bot's generic error reply instead of a policy notice.
- Elsewhere, the mobile app shows a plain failure. In group chats and conversations that aren't the member's own, it shows "Bot failed to respond" with "Blocked by your organization's policy. Send a different message, or contact your admin if this keeps happening," without the deny reason, your Blocked Message, or buttons.
- Other Cursor apps show only the title.
- Older desktop versions show no buttons. Depending on the version, the notice shows the deny reason and your Blocked Message with the guidance "Send a different message, or start a new chat if this keeps happening," or only "Blocked by your organization's policy. Send a different message, or contact your admin if this keeps happening."
- Background helpers stop without a notice. When a helper Grok Bot started is blocked, the Bot learns your organization's policy stopped it, without the deny reason.
- Routines stop and record the block. The run history in the Cursor dashboard shows Blocked by your organization's policy, or Your organization's policy check is unavailable for a failed check in Block mode, with a fixed explanation and no deny reason. The Grok Bot app's routine list shows only that the run failed.
- Email, Linear, and other connected channels get no reply.
A blocked message stays in the conversation until someone takes it back or resets the conversation. Until then, Grok Bot sends it with every later request, so your server judges it again each time. A policy that scans the whole transcript keeps denying that conversation. A policy that judges the newest user message lets it continue once the member sends something different.
Members see nothing when a request is allowed or when you're in Shadow mode. Shadow mode sends their conversations to your endpoint too, so tell members your organization checks Grok Bot requests before you turn on Enforce Verdicts.
Coverage
Covered: the model requests behind your team members' Grok Bot conversations. That includes every step of a turn, subagents, the summaries Grok Bot writes of long conversations, and routines. A team Bot or room follows its team's inference hook. A personal Bot follows the team its owner has selected. Each request gets its own frame with the conversation so far.
Not covered:
- Model responses aren't checked. See How it works.
- Parts of a request that aren't conversation stay out. System prompts, tool definitions, and the model's hidden reasoning never go to your endpoint, and Cursor leaves out most of the text it adds for the model, such as reminders and timestamps.
- Ancillary model calls aren't sent. These calls support a conversation rather than continue it, such as writing chat titles, Auto Review, and saving memories.
- Other Cursor features aren't covered. That includes Cloud Agents, agents in the editor, the CLI, the Agent SDK, Tab, embeddings, and local models.
- Actions aren't checked as they run. Tool execution, connected-app actions, and network traffic from a member's Grok Bot computer reach your endpoint only when their output returns to the model as a tool result in a later frame. Use the network policy and Auto Review to control actions.
Request format
Cursor sends each frame as an HTTPS POST with a compact JSON body. Header
names are lowercase.
| Header | Value |
|---|---|
content-type | application/json |
user-agent | cursor-inference-hooks/1 |
accept-encoding | identity |
webhook-id, webhook-timestamp, webhook-signature | See Signing secret |
| Your custom headers | As saved |
Standard headers such as host and content-length come with every request
too.
JSON
{
"type": "prompt",
"request_id": "req_6b1f0c2e-4d7a-4f1e-9a53-2c8d7e41b9a0",
"tenant_id": "team_4mQvT8yLcN2pR6",
"actor": {
"type": "user",
"id": "user_8hJw3KzP5sXq1V7nB2cF9mD4tR",
"email_address": null
},
"source": { "application": "grok-bot" },
"session_id": "3f2c9a71-58e4-4b0d-9c6a-0d1e7b25a4f8",
"model": "<Cursor model identifier>",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Check the Q3 renewals sheet for accounts at risk and draft a note to the account team."
},
{
"type": "attachment",
"file_name": null,
"media_type": "image/png",
"size_bytes": 184320,
"text": null
}
]
}
],
"metadata": {}
}
Frame fields
| Field | Value |
|---|---|
type | Always prompt |
request_id | A unique ID for the request. It matches webhook-id and the audit log's request_id |
tenant_id | An opaque ID for your team |
actor.type | Always user |
actor.id | An opaque ID for the member who sent the message, or for a team service account. Null for Slack senders who haven't linked a Cursor account |
actor.email_address | Always null |
source.application | grok-bot, or config-test for Test Connection and circuit-breaker recovery checks |
session_id | An opaque ID for the Bot, shared across its chats and members, so it doesn't identify one conversation |
model | A Cursor model identifier. Use it for reporting, not allowlisting |
messages | The conversation so far. See Messages |
metadata | Always empty |
Messages
- Roles are
userandassistant. Tool results sit underuser. - Blocks are
text,tool_use,tool_result, andattachment. Atool_useblock names the tool and holds itsinput, and the matchingtool_resultholds the output. Context Grok Bot gives the model, such as rules, shared memory, and the connected apps it can use, usually arrives in anattachmentblock'stext. An image is anattachmentwith its media type and size, but not its bytes or URL. - Every frame holds the whole conversation so far. Frames aren't deltas, so each one carries the earlier messages again. In a long conversation, a summary Grok Bot writes takes the place of older messages.
- Replies to members are tool calls. Grok Bot sends what members read
through a tool, so its replies appear in
tool_useinput, not in assistanttext. - Cursor never adds secret values. Context can name and describe the secrets a Bot can use, but never includes their values.
Write policies for these frames
- Verify the signature. Don't rely on the User-Agent or source IP. If the Inference Hooks page lists Source IP Ranges under Request Signing, you can allowlist them as well.
- Treat IDs as opaque strings. Don't parse
tenant_idoractor.id. Map members byactor.id, since frames carry no email. - Treat
source.applicationas an open set. Cursor adds values as coverage grows. It's advisory, not a security control. - Scan every block type, not only
text. Context usually arrives in attachmenttext, and replies to members and connected-app calls arrive intool_useinput. - Don't rely on
tool_result.is_error. It'sfalsefor most failed tool calls too.
Verdicts
Answer with HTTP 200 and a JSON object:
JSON
{
"action": "deny",
"deny_reason": "This message contains a customer account number. Remove it and try again.",
"reference_id": "policy-7f3c21"
}
actionisallowordeny. Anything else is a failed check.deny_reasonis shown in the block notice to the member who sent the message. Cursor truncates it to 500 characters.reference_idis your identifier for the decision, recorded in the Cursor audit log. Up to 50 characters fromA-Z,a-z,0-9, and.,_,:,/,-. A malformed value is dropped and the deny still applies.- Unknown fields are ignored. Cursor reads at most 64 KiB, uncompressed, and ignores cookies.
Answer config-test frames from Test Connection and circuit-breaker recovery
with a verdict too, and keep them out of your reports on member traffic.
Audit log
Inference hook activity appears in your team's audit log. Payloads carry identifiers and codes, never prompt content, the deny reason, the signing secret, or header values. An allowed request writes no event.
event_type | When it fires | Fields |
|---|---|---|
inference_hooks_config_updated | An admin saves settings or resets the circuit breaker, or automatic recovery resets it | action (update, reset_circuit_breaker, circuit_breaker_recovered), changed_fields, enforce_verdicts, mode, endpoint_host, verdict_timeout_ms, rollout_percent, enabled_surfaces, config_version |
inference_hooks_signing_secret_generated | The first save that sets an endpoint generates the signing secret, or an admin rotates it | action (generate_signing_secret, rotate_signing_secret), changed_fields (always empty), enforce_verdicts, mode, endpoint_host, verdict_timeout_ms, rollout_percent, enabled_surfaces, config_version |
inference_hooks_request_denied | Your server denied a request | request_id, reference_id, source_application, session_id, model, actor_id, mode, enforced (false in Shadow mode) |
inference_hooks_request_failed_open | A request proceeded without a verdict: a failed check in Allow or Shadow mode, a Cursor pause, or settings Cursor couldn't load | request_id, reason, source_application, session_id, model, actor_id, mode, status_code (0 when no response arrived) |
inference_hooks_request_failed_closed | A check failed in Block mode, so the request was blocked | request_id, reason, source_application, session_id, model, actor_id, mode, status_code (0 when no response arrived) |
inference_hooks_circuit_breaker_tripped | Sustained failures tripped the circuit breaker | config_version, mode, failure_count, window_seconds, last_failure_kind |
On inference_hooks_signing_secret_generated, the settings fields show the
state after the change. A rotation bumps config_version. A first secret
shares it with the inference_hooks_config_updated event from the same save.
Cursor records request failures in every mode, as failed open in Allow and
Shadow and failed closed in Block, and it records automatic circuit-breaker
recovery. reason on the failed-open and failed-closed events is a failure
(timeout, non_200_status, unparseable_response, oversized_response,
invalid_action, transport_error, tls_error, endpoint_rejected,
frame_too_large), a Cursor-side cause (endpoint_not_allowlisted,
config_unavailable), or, for failed open only, a pause (kill_switch). Build
SIEM rules on these event names and reason values.
The request_id on denied and failed events matches the frame's request_id
and webhook-id, so you can join your vendor's records to the audit log.
The Inference Hooks page also shows endpoint health: status, verdict latency, failures per minute, block rate over 24 hours, and recent errors with their type and status code. Its figures don't cover all traffic, so they can be lower than your vendor's counts.
Privacy and Privacy Mode
- Cursor doesn't keep what it sends to your hook. Cursor sends each frame to your endpoint over HTTPS and discards it once the verdict arrives. Frame content isn't written to Cursor's databases, logs, metrics, or traces.
- Your endpoint, your retention. The endpoint belongs to your organization or your security vendor. What it stores, where, and for how long depends on your configuration and your agreement with that vendor. Cursor doesn't control that data.
- Privacy Mode is unchanged. The model request itself is handled exactly as it is without a hook, under your team's Privacy Mode setting, and the hook adds no copy of it. Inference hooks run for every covered member regardless of Privacy Mode, because your admins direct Cursor to send these requests to your own endpoint.
- Frame fields identify people only by opaque IDs.
actor.email_addressis always null.
Cursor keeps only what it needs to run the feature and show you what happened:
- Your settings, with secrets encrypted. Header values and the signing secret are stored encrypted.
- Audit log events with identifiers and codes only. They include your
server's
reference_id, as listed in Audit log. - Health figures without content. They hold counts, latencies, and recent error types and status codes.
- The block notice, encrypted with the blocked turn. Your server's
deny_reasonand your Blocked Message are stored, encrypted, with Grok Bot's record of the blocked turn.
Troubleshooting
Why don't I see Inference Hooks on the Grok Bot page?
The page appears for team admins on Enterprise teams, and it shows the team selected in the dashboard. Check that the right team is selected. If the page says "Inference hooks aren't available yet," contact your account manager.
Why is my endpoint URL rejected?
Check the endpoint requirements. The usual causes are a port other than 443, a query string or credentials in the URL, a tunnel host, or a host that resolves to a private address.
Why does Test Connection say a signing secret is required?
Cursor generates the secret on the first save that sets an endpoint. Save the endpoint, copy the secret into your vendor's console, then test again.
Why does my server reject Cursor's requests?
Most rejections are a signature or credential mismatch. Paste the current
signing secret into your server, including the whsec_ prefix if your verifier
expects it, and check that it decodes the secret as standard base64. Confirm
your API key header is saved. If you changed the endpoint URL, re-enter the
header values. After a rotation, Cursor switches to the new secret within about
a minute.
Why does Test Connection return a deny?
Your server's policy denies Cursor's synthetic test prompt. In Allow or Block mode, a policy like that blocks members too. Adjust the policy or its default action before you turn on enforcement.
Why are requests timing out?
Your server took longer than the verdict timeout. Check verdict latency in the health panel and your vendor's reports. Lighter scan profiles answer faster. You can raise Verdict Timeout (ms) up to 10,000.
Why isn't my endpoint receiving requests?
Check that an endpoint is saved, Enforce Verdicts is on, Coverage includes Grok Bot, Requests Inspected (%) is above 0, and the circuit breaker isn't tripped. Only Grok Bot conversations of your team's members are covered. If Test Connection reports that inference hooks are paused, Cursor has paused them.
Why do members see that the policy check is unavailable?
You're in Block mode and the check failed, or the circuit breaker tripped. Check Recent Errors on the Inference Hooks page. To keep members working while you fix the endpoint, switch to Allow the Request.
The circuit breaker tripped. How do I recover?
Fix the endpoint, run Test Connection, then click Reset. Automatic recovery stops after any settings change, so don't wait for it if you edited anything after the trip.
Why is every message in a conversation blocked after one block?
The blocked message stays in the conversation and goes to your server with every later request, so a policy that scans the whole transcript denies each of them. Choose Edit on the block notice in the desktop or mobile app, or Remove blocked message on the Bot's reply in Slack, to take the message back and send something different. Reset clears the conversation instead. If the conversation is blocked again right after a reset, what your policy matches is likely in the Bot's memory or files. Remove it there. A policy that judges the newest user message lets a conversation continue once the member sends something different.
Why don't inference hook events appear in the audit log filter?
The event types appear in the filter only while inference hooks are enabled for your team. Events that were recorded still show in the log.
Related pages
Last updated: October 22, 2026