Guide to Cursor Webhooks: Features and Best Practices
Cursor webhooks notify your application when a Cloud Agent finishes or errors. If you kick off background coding agents and need to react when they complete, open a pull request, or fail, webhooks are how you learn about it without polling.
This guide covers how Cursor Cloud Agent webhooks work, the statusChange event, how to verify the X-Webhook-Signature, and the best practices for production.
What are Cursor webhooks?
Cursor Cloud Agent webhooks are JSON POSTs delivered to a URL you configure. Each is signed with an X-Webhook-Signature header formatted sha256=<hex>: an HMAC-SHA256 over the raw request body. Cursor also sends an X-Webhook-Id (a unique delivery ID) and an X-Webhook-Event (the event type, statusChange).
Cursor webhook features
| Feature | Details |
|---|---|
| Configuration | Cursor dashboard > Cloud Agent settings > Webhooks |
| Signature header | X-Webhook-Signature, formatted sha256=<hex> |
| Signature scheme | HMAC-SHA256 (hex) over the raw body, keyed with the webhook secret |
| Extra headers | X-Webhook-Id (delivery ID), X-Webhook-Event (statusChange) |
| Payload | event, timestamp, id, status, source, target, summary |
| SDK | None; verify manually |
Common events
Cursor has a single event type, statusChange, whose status field carries the outcome:
status | Fires when |
|---|---|
FINISHED | The agent completed successfully |
ERROR | The agent encountered an error |
Branch on the status field, not on separate event names, and read target.prUrl / target.branchName when an agent opens a pull request.
Setting up Cursor webhooks
In the Cursor dashboard > Cloud Agent settings > Webhooks, add your endpoint URL, select the statusChange event, and copy the webhook signing secret into CURSOR_WEBHOOK_SECRET. On setup, Cursor sends a test statusChange event, verify it and return 200.
Securing Cursor webhooks
Split the X-Webhook-Signature on =, confirm the algorithm is sha256, compute an HMAC-SHA256 over the raw body, hex-encode it, and compare in constant time. Verify against the raw body before parsing.
const crypto = require("crypto");
const SECRET = process.env.CURSOR_WEBHOOK_SECRET;
function verify(rawBody, signatureHeader) {
if (!signatureHeader) return false;
const parts = signatureHeader.split("=");
if (parts.length !== 2 || parts[0] !== "sha256") return false;
const expected = crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
const a = Buffer.from(parts[1]);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhooks/cursor", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body, req.headers["x-webhook-signature"])) {
return res.sendStatus(401);
}
res.sendStatus(200); // acknowledge fast
const payload = JSON.parse(req.body.toString());
processQueue.add(payload); // branch on payload.status, dedupe on X-Webhook-Id, async
});
The same check in Python:
import hashlib
import hmac
import os
SECRET = os.environ["CURSOR_WEBHOOK_SECRET"].encode()
def verify(raw_body: bytes, signature_header: str) -> bool:
if not signature_header:
return False
parts = signature_header.split("=")
if len(parts) != 2 or parts[0] != "sha256":
return False
expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts[1], expected)
Cursor webhook limitations and pain points
One event type, status in the payload
The Problem: There's only statusChange. Code that expects distinct FINISHED or ERROR events never fires, because those are status field values.
Why It Happens: Cursor models outcomes as a status field on a single event.
Workarounds:
- Branch on
payload.status(FINISHED/ERROR), and readtargetfor PR details.
How Hookdeck Can Help: Hookdeck's filters can route on the status field, giving you outcome-based routing.
Verify over the raw body
The Problem: The HMAC is over the exact bytes Cursor sent. Parsing and re-serializing the JSON changes the bytes and breaks verification.
Why It Happens: JSON re-serialization reorders keys and whitespace.
Workarounds:
- Capture the raw body (
express.raw) and verify before parsing.
How Hookdeck Can Help: Hookdeck verifies against the received bytes at the edge.
Dedupe on X-Webhook-Id
The Problem: Retried deliveries repeat the same event, and the signature proves authenticity, not uniqueness.
Why It Happens: At-least-once delivery.
Workarounds:
- Dedupe on the
X-Webhook-Iddelivery ID and make handlers idempotent.
How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge. See our guide to webhook idempotency.
A single, fresh topic
The Problem: Cloud Agent webhooks are new, so patterns and tooling are still thin, and there's no official SDK verification helper.
Why It Happens: It's a recent feature.
Workarounds:
- Verify manually with the HMAC-SHA256 check above.
How Hookdeck Can Help: Hookdeck gives you verification, observability, and retries at the edge, so a young webhook source is production-ready without waiting for an SDK.
Best practices
Verify HMAC-SHA256 over the raw body
Confirm the sha256= prefix, compute the hex HMAC over the raw body, and compare in constant time.
Branch on the status field
Handle FINISHED and ERROR from payload.status, and read target for PR and branch details.
Dedupe on X-Webhook-Id
Persist the delivery ID and skip repeats.
Acknowledge fast, process asynchronously
Return 200 quickly and defer work to a queue. See why to process webhooks asynchronously.
Make Cursor webhooks production-ready
Hookdeck verifies X-Webhook-Signature, deduplicates, and durably queues every Cloud Agent event
Conclusion
Cursor Cloud Agent webhooks are verified with an X-Webhook-Signature HMAC-SHA256 over the raw body, formatted sha256=<hex>, alongside X-Webhook-Id and X-Webhook-Event headers. There's one event, statusChange, whose status is FINISHED or ERROR. Verify over the raw body, branch on the status, and dedupe on the delivery ID.
Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.
Get started with Hookdeck for free and handle Cursor webhooks reliably in minutes.