cursor-webhooks
Receive and verify Cursor Cloud Agent webhooks. Use when setting up Cursor webhook handlers, debugging signature verification, or handling Cloud Agent status change events (ERROR, FINISHED).
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install hookdeck-webhook-skills-cursor-webhooks
Repository
Skill path: skills/cursor-webhooks
Receive and verify Cursor Cloud Agent webhooks. Use when setting up Cursor webhook handlers, debugging signature verification, or handling Cloud Agent status change events (ERROR, FINISHED).
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Testing, Integration.
Target audience: everyone.
License: MIT.
Original source
Catalog source: SkillHub Club.
Repository owner: hookdeck.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install cursor-webhooks into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/hookdeck/webhook-skills before adding cursor-webhooks to shared team environments
- Use cursor-webhooks for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: cursor-webhooks
description: >
Receive and verify Cursor Cloud Agent webhooks. Use when setting up Cursor
webhook handlers, debugging signature verification, or handling Cloud Agent
status change events (ERROR, FINISHED).
license: MIT
metadata:
author: hookdeck
version: "0.1.0"
repository: https://github.com/hookdeck/webhook-skills
---
# Cursor Webhooks
## When to Use This Skill
- Setting up Cursor Cloud Agent webhook handlers
- Debugging signature verification failures
- Understanding Cursor webhook event types and payloads
- Handling Cloud Agent status change events (ERROR, FINISHED)
## Essential Code (USE THIS)
### Cursor Signature Verification (JavaScript)
```javascript
const crypto = require('crypto');
function verifyCursorWebhook(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
// Cursor sends: sha256=xxxx
const [algorithm, signature] = signatureHeader.split('=');
if (algorithm !== 'sha256') return false;
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch {
return false;
}
}
```
### Express Webhook Handler
```javascript
const express = require('express');
const app = express();
// CRITICAL: Use express.raw() - Cursor requires raw body for signature verification
app.post('/webhooks/cursor',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'];
const webhookId = req.headers['x-webhook-id'];
const event = req.headers['x-webhook-event'];
// Verify signature
if (!verifyCursorWebhook(req.body, signature, process.env.CURSOR_WEBHOOK_SECRET)) {
console.error('Cursor signature verification failed');
return res.status(401).send('Invalid signature');
}
// Parse payload after verification
const payload = JSON.parse(req.body.toString());
console.log(`Received ${event} (id: ${webhookId})`);
// Handle status changes
if (event === 'statusChange') {
console.log(`Agent ${payload.id} status: ${payload.status}`);
if (payload.status === 'FINISHED') {
console.log(`Summary: ${payload.summary}`);
} else if (payload.status === 'ERROR') {
console.error(`Agent error for ${payload.id}`);
}
}
res.json({ received: true });
}
);
```
### Python Signature Verification (FastAPI)
```python
import hmac
import hashlib
from fastapi import Request, HTTPException
def verify_cursor_webhook(body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header or not secret:
return False
# Cursor sends: sha256=xxxx
parts = signature_header.split('=')
if len(parts) != 2 or parts[0] != 'sha256':
return False
signature = parts[1]
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
# Timing-safe comparison
return hmac.compare_digest(signature, expected)
```
## Common Event Types
| Event Type | Description | Common Use Cases |
|------------|-------------|------------------|
| `statusChange` | Agent status changed | Monitor agent completion, handle errors |
### Event Payload Structure
```json
{
"event": "statusChange",
"timestamp": "2024-01-01T12:00:00.000Z",
"id": "agent_123456",
"status": "FINISHED", // or "ERROR"
"source": {
"repository": "https://github.com/user/repo",
"ref": "main"
},
"target": {
"url": "https://github.com/user/repo/pull/123",
"branchName": "feature-branch",
"prUrl": "https://github.com/user/repo/pull/123"
},
"summary": "Updated 3 files and fixed linting errors"
}
```
## Environment Variables
```bash
# Your Cursor webhook signing secret
CURSOR_WEBHOOK_SECRET=your_webhook_secret_here
```
## Local Development
For local webhook testing, install Hookdeck CLI:
```bash
# Install via npm
npm install -g hookdeck-cli
# Or via Homebrew
brew install hookdeck/hookdeck/hookdeck
```
Then start the tunnel:
```bash
hookdeck listen 3000 --path /webhooks/cursor
```
No account required. Provides local tunnel + web UI for inspecting requests.
## Resources
- `overview.md` - What Cursor webhooks are, event types
- `setup.md` - Configure webhooks in Cursor dashboard
- `verification.md` - Signature verification details and gotchas
- `examples/` - Runnable examples per framework
## Recommended: webhook-handler-patterns
For production-ready webhook handling, also use the webhook-handler-patterns skill:
- [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md)
- [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md)
- [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md)
- [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md)
## Related Skills
- [stripe-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks) - Stripe webhook handling
- [github-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/github-webhooks) - GitHub webhook handling
- [shopify-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/shopify-webhooks) - Shopify webhook handling
- [openai-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/openai-webhooks) - OpenAI webhook handling
- [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) - Idempotency, error handling, retry logic
- [hookdeck-event-gateway](https://github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway) - Webhook infrastructure that replaces your queue — guaranteed delivery, automatic retries, replay, rate limiting, and observability for your webhook handlers
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### references/overview.md
```markdown
# Cursor Webhooks Overview
## What Are Cursor Webhooks?
Cursor Cloud Agent webhooks are HTTP callbacks that notify your application when agent status changes occur. These webhooks enable real-time monitoring of Cloud Agent operations, allowing you to track when agents complete tasks or encounter errors.
## Common Event Types
| Event | Triggered When | Common Use Cases |
|-------|----------------|------------------|
| `statusChange` | Agent status changes to ERROR or FINISHED | Monitor agent completion, handle errors, update UI |
## Event Payload Structure
All Cursor webhooks share a consistent payload structure:
```json
{
"event": "statusChange",
"timestamp": "2024-01-01T12:00:00.000Z",
"id": "agent_123456",
"status": "FINISHED",
"source": {
"repository": "https://github.com/user/repo",
"ref": "main"
},
"target": {
"url": "https://github.com/user/repo/pull/123",
"branchName": "feature-branch",
"prUrl": "https://github.com/user/repo/pull/123"
},
"summary": "Updated 3 files and fixed linting errors"
}
```
### Status Values
- `FINISHED` - Agent completed successfully
- `ERROR` - Agent encountered an error
## HTTP Headers
Cursor sends these headers with every webhook:
| Header | Description | Example |
|--------|-------------|---------|
| `X-Webhook-Signature` | HMAC-SHA256 signature | `sha256=abc123...` |
| `X-Webhook-ID` | Unique delivery ID | `msg_01234567890` |
| `X-Webhook-Event` | Event type | `statusChange` |
| `User-Agent` | Identifies Cursor webhooks | `Cursor-Agent-Webhook/1.0` |
| `Content-Type` | Payload format | `application/json` |
## Full Event Reference
For the complete list of events and detailed specifications, see [Cursor's webhook documentation](https://cursor.com/docs/cloud-agent/api/webhooks).
```
### references/setup.md
```markdown
# Setting Up Cursor Webhooks
## Prerequisites
- Cursor Cloud Agent access
- Your application's webhook endpoint URL
- Admin access to configure webhooks in your Cursor settings
## Get Your Signing Secret
1. Log in to your Cursor dashboard
2. Navigate to Cloud Agent settings
3. Go to the Webhooks section
4. Copy your webhook signing secret
- Keep this secret secure
- Never commit it to version control
- Rotate it periodically for security
## Register Your Endpoint
1. In the Cursor Cloud Agent settings, click "Add Webhook"
2. Enter your webhook endpoint URL:
- Production: `https://yourdomain.com/webhooks/cursor`
- Development: Use Hookdeck CLI tunnel URL
3. Select the events to receive:
- `statusChange` - Notifies when agent status changes
4. Save the webhook configuration
## Test Your Webhook
1. Cursor will send a test `statusChange` event to verify your endpoint
2. Your endpoint should:
- Return a 200 status code
- Verify the signature
- Process the test payload
## Environment Configuration
Add your signing secret to your environment:
```bash
# .env file
CURSOR_WEBHOOK_SECRET=your_webhook_secret_here
```
## Security Best Practices
- Always verify webhook signatures
- Use HTTPS endpoints only
- Store secrets in environment variables
- Implement request timeouts
- Log webhook events for debugging
- Return 200 quickly, process asynchronously if needed
```
### references/verification.md
```markdown
# Cursor Signature Verification
## How It Works
Cursor uses HMAC-SHA256 to sign webhook payloads. The signature is sent in the `X-Webhook-Signature` header with the format `sha256=<hex_digest>`.
The signature is computed by:
1. Taking the raw request body (before parsing)
2. Creating an HMAC-SHA256 hash using your webhook secret
3. Encoding the result as hexadecimal
4. Prefixing with `sha256=`
## Implementation
### Manual Verification (Recommended)
```javascript
const crypto = require('crypto');
function verifyCursorWebhook(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) {
return false;
}
// Extract algorithm and signature
const [algorithm, signature] = signatureHeader.split('=');
if (algorithm !== 'sha256') {
return false;
}
// Calculate expected signature
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Timing-safe comparison
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
} catch {
return false; // Different lengths
}
}
```
### Python Implementation
```python
import hmac
import hashlib
def verify_cursor_webhook(body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header or not secret:
return False
# Extract algorithm and signature
parts = signature_header.split('=')
if len(parts) != 2 or parts[0] != 'sha256':
return False
signature = parts[1]
# Calculate expected signature
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
# Timing-safe comparison
return hmac.compare_digest(signature, expected)
```
## Common Gotchas
### 1. Raw Body Parsing
**Problem**: Using parsed JSON instead of raw body breaks signature verification.
**Solution**: Always use the raw request body:
```javascript
// Express
app.use('/webhooks/cursor', express.raw({ type: 'application/json' }));
// Next.js
export const config = { api: { bodyParser: false } };
// FastAPI
body = await request.body() # Get raw bytes
```
### 2. Header Case Sensitivity
**Problem**: Some frameworks lowercase headers.
**Solution**: Access headers case-insensitively:
```javascript
// Express normalizes to lowercase
const signature = req.headers['x-webhook-signature'];
// FastAPI preserves case
signature = request.headers.get('X-Webhook-Signature')
```
### 3. Missing Timing-Safe Comparison
**Problem**: Using `===` for comparison is vulnerable to timing attacks.
**Solution**: Always use timing-safe comparison:
```javascript
// Good
crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))
// Bad
signature === expected
```
### 4. Incorrect Secret Format
**Problem**: Using the wrong secret or format.
**Solution**: Use the exact secret from Cursor dashboard, no modifications.
## Debugging Verification Failures
If signature verification fails:
1. **Check the raw body**: Log the exact bytes being signed
2. **Verify the secret**: Ensure no extra whitespace or encoding issues
3. **Check headers**: Log the exact signature header value
4. **Compare signatures**: Log both calculated and received signatures (in development only)
### Debug Helper
```javascript
function debugWebhook(rawBody, signatureHeader, secret) {
console.log('=== Webhook Debug ===');
console.log('Body length:', rawBody.length);
console.log('Body preview:', rawBody.toString().substring(0, 100));
console.log('Signature header:', signatureHeader);
const [algorithm, signature] = signatureHeader.split('=');
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
console.log('Received sig:', signature);
console.log('Expected sig:', expected);
console.log('Match:', signature === expected);
console.log('===================');
}
```
**Important**: Only use debug logging in development. Never log signatures or secrets in production.
```