Agents
Cloud Agent Webhooks: Status Notifications
When you create a Cloud Agent with a webhook URL, Cursor sends HTTP POST requests to that URL to report status changes. Only statusChange events are supported today, fired when an agent reaches an ERROR or FINISHED state. Each request carries an X-Webhook-Signature header holding an HMAC-SHA256 signature, which you verify against the raw request body.

On this page
When does Cursor send a Cloud Agent webhook?
Only when you gave the agent a webhook URL at creation. From then on, Cursor makes an HTTP POST request to that URL each time the agent's status changes in one of two ways: it encounters an ERROR state, or it reaches FINISHED. The event type is statusChange, and the docs say it is currently the only one supported.
That is a narrow surface, and it is worth being clear about what it leaves out. Nothing here announces that an agent started or is partway through a task, so a webhook tells you a run has ended, one way or the other, and nothing finer than that.
The word "currently" in the docs leaves room for more event types later, though it promises none.
Two details from the docs make the trigger concrete. The webhook URL is supplied when you create the agent, and the event type is also sent in the X-Webhook-Event header, where the docs again say it is currently only statusChange. The page does not describe changing the URL after the agent exists.
This is covered hands-on in Agent Mode Foundations — 6 short Units, free to read.
Rather do it than read about it? Run 11 interactive Cursor walkthroughs in a simulated editor. Free, no account needed.
How do I verify a webhook really came from Cursor?
Recompute the signature and compare it with the one Cursor sent. Every request is signed with HMAC-SHA256 and the result arrives in the X-Webhook-Signature header as sha256=<hex_digest>. Three other headers travel with it, and the table below lists all four.
- Header
X-Webhook-Signature- What it carries
- HMAC-SHA256 signature, formatted
sha256=<hex_digest>
- Header
X-Webhook-ID- What it carries
- A unique identifier for this delivery, useful for logging
- Header
X-Webhook-Event- What it carries
- The event type, currently only
statusChange
- Header
User-Agent- What it carries
- Always
Cursor-Agent-Webhook/1.0
| Header | What it carries |
|---|---|
X-Webhook-Signature | HMAC-SHA256 signature, formatted sha256=<hex_digest> |
X-Webhook-ID | A unique identifier for this delivery, useful for logging |
X-Webhook-Event | The event type, currently only statusChange |
User-Agent | Always Cursor-Agent-Webhook/1.0 |
Headers on every Cloud Agent webhook request.
Of the four, X-Webhook-Signature is the one used for verification. The docs describe X-Webhook-ID as useful for logging and X-Webhook-Event as the event type.
The docs are explicit about the body: always compute the signature over the raw request body, before any parsing. The signature covers the bytes Cursor sent, so a framework that parses the JSON and hands you back a re-serialized copy can change spacing or key order, and a genuine request then fails the check. Capture the body first, verify it, and only then parse.
const crypto = require("crypto"); function verifyWebhook(secret, rawBody, signature) { const expectedSignature = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); return signature === expectedSignature; }
import hmac
import hashlib
def verify_webhook(secret, raw_body, signature):
expected_signature = 'sha256=' + hmac.new(
secret.encode(),
raw_body,
hashlib.sha256
).hexdigest()
return signature == expected_signatureBoth functions take the same three inputs: a secret, the raw body and the received signature, and both return whether the two signatures match. The docs page does not say where the secret is set up.
Reading the two samples side by side shows the same three steps. Each builds the expected value by prefixing sha256= to the hex digest of an HMAC-SHA256 over the raw body, keyed with the secret, and then compares it with the header value. The Node version uses crypto.createHmac("sha256", secret).update(rawBody).digest("hex"), and the Python version uses hmac.new with hashlib.sha256 and .hexdigest(). Both compare with a plain equality check, and the docs show no other comparison method.
What does the webhook payload contain?
A JSON body with the event name, a timestamp, the agent's id, its status, and a summary, plus nested source and target objects. The docs note that some fields are optional and appear only when available. The example below is the one the docs give, for a run that finished.
{
"event": "statusChange",
"timestamp": "2024-01-15T10:30:00Z",
"id": "bc_abc123",
"status": "FINISHED",
"source": {
"repository": "https://github.com/your-org/your-repo",
"ref": "main"
},
"target": {
"url": "https://cursor.com/agents?id=bc_abc123",
"branchName": "cursor/add-readme-1234",
"prUrl": "https://github.com/your-org/your-repo/pull/1234"
},
"summary": "Added README.md with installation instructions"
}- Field
event- Value in the docs example
statusChange
- Field
timestamp- Value in the docs example
2024-01-15T10:30:00Z
- Field
id- Value in the docs example
bc_abc123
- Field
status- Value in the docs example
FINISHED
- Field
source.repository- Value in the docs example
- A GitHub repository URL
- Field
source.ref- Value in the docs example
main
- Field
target.url- Value in the docs example
- A cursor.com/agents link for the run
- Field
target.branchName- Value in the docs example
cursor/add-readme-1234
- Field
target.prUrl- Value in the docs example
- A GitHub pull request URL
- Field
summary- Value in the docs example
- One line describing what the agent did
| Field | Value in the docs example |
|---|---|
event | statusChange |
timestamp | 2024-01-15T10:30:00Z |
id | bc_abc123 |
status | FINISHED |
source.repository | A GitHub repository URL |
source.ref | main |
target.url | A cursor.com/agents link for the run |
target.branchName | cursor/add-readme-1234 |
target.prUrl | A GitHub pull request URL |
summary | One line describing what the agent did |
Fields in the documented payload and the example value for each.
A small detail in the example: the id also appears as the id query parameter of target.url, so either one is enough to point a person back at the run in Cursor. The source object says where the work started, a repository and a ref, and the target object says where it landed: the run's page, the branch name and a pull request URL.
Because only two states trigger a send, the status field should read either ERROR or FINISHED, which means there are two values to expect. The summary and the target fields carry the human-readable part of the result, the one-line description and the links back to the run and its pull request.
The docs do not say which fields are the optional ones. The documented example shows every field.
How should my webhook endpoint behave?
The docs give five practices for the receiving side. They read as a short checklist, and the list below keeps them in the docs' own terms.
- Verify signatures: always check the signature to confirm the request is from Cursor.
- Handle retries: webhooks may be retried if your endpoint returns an error status code.
- Return quickly: return a 2xx status code as soon as possible.
- Use HTTPS: use HTTPS URLs for webhook endpoints in production.
- Store raw payloads: keep the raw payload for debugging and future verification.
Two of these are linked. Retries happen when the endpoint answers with an error status, and the instruction is to return a 2xx status code as soon as possible. The docs give no timeout value for that, so "quickly" is the only guidance. They also say to use HTTPS URLs for webhook endpoints in production, which is the one practice on the list that concerns the URL you supply when you create the agent.
Storing the raw payload ties back to verification. You need the raw bytes to recompute a signature at all, and keeping them means a delivery you doubted yesterday can still be checked today.
Several things are outside what the page documents, and this article has not filled them in: where the signing secret is configured, which payload fields are optional, how many times and how often a delivery is retried, and whether event types beyond statusChange are planned. The page is a short reference, and the five best practices above are all it says about operating an endpoint.
The page says webhooks may be retried after an error status. It does not say how many times, over how long, or whether a retried delivery reuses the same X-Webhook-ID. The documentation is silent on those points.
Frequently asked questions
Which events do Cloud Agent webhooks send?
Only statusChange events are supported at the moment. Cursor sends one when an agent encounters an ERROR state or reaches FINISHED, and the event type is repeated in the X-Webhook-Event header.
How do I verify a Cursor webhook signature?
Compute an HMAC-SHA256 of the raw request body using your secret, prefix the hex digest with sha256=, and compare it with the X-Webhook-Signature header. Use the raw body before any parsing, since a re-serialized copy can differ from what Cursor signed.
Does a Cloud Agent webhook always include every payload field?
No. The docs say some fields are optional and are included only when available, without listing which ones. The documented example has event, timestamp, id, status, source, target and summary.
Sources & last verified
Cursor ships frequently. Last updated October 17, 2026.