$npx -y skills add hookdeck/webhook-skills --skill cursor-webhooksReceive 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).
| 1 | # Cursor Webhooks |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | |
| 5 | - Setting up Cursor Cloud Agent webhook handlers |
| 6 | - Debugging signature verification failures |
| 7 | - Understanding Cursor webhook event types and payloads |
| 8 | - Handling Cloud Agent status change events (ERROR, FINISHED) |
| 9 | |
| 10 | ## Essential Code (USE THIS) |
| 11 | |
| 12 | ### Cursor Signature Verification (JavaScript) |
| 13 | |
| 14 | ```javascript |
| 15 | const crypto = require('crypto'); |
| 16 | |
| 17 | function verifyCursorWebhook(rawBody, signatureHeader, secret) { |
| 18 | if (!signatureHeader || !secret) return false; |
| 19 | |
| 20 | // Cursor sends: sha256=xxxx |
| 21 | const [algorithm, signature] = signatureHeader.split('='); |
| 22 | if (algorithm !== 'sha256') return false; |
| 23 | |
| 24 | const expected = crypto |
| 25 | .createHmac('sha256', secret) |
| 26 | .update(rawBody) |
| 27 | .digest('hex'); |
| 28 | |
| 29 | try { |
| 30 | return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); |
| 31 | } catch { |
| 32 | return false; |
| 33 | } |
| 34 | } |
| 35 | ``` |
| 36 | |
| 37 | ### Express Webhook Handler |
| 38 | |
| 39 | ```javascript |
| 40 | const express = require('express'); |
| 41 | const app = express(); |
| 42 | |
| 43 | // CRITICAL: Use express.raw() - Cursor requires raw body for signature verification |
| 44 | app.post('/webhooks/cursor', |
| 45 | express.raw({ type: 'application/json' }), |
| 46 | (req, res) => { |
| 47 | const signature = req.headers['x-webhook-signature']; |
| 48 | const webhookId = req.headers['x-webhook-id']; |
| 49 | const event = req.headers['x-webhook-event']; |
| 50 | |
| 51 | // Verify signature |
| 52 | if (!verifyCursorWebhook(req.body, signature, process.env.CURSOR_WEBHOOK_SECRET)) { |
| 53 | console.error('Cursor signature verification failed'); |
| 54 | return res.status(401).send('Invalid signature'); |
| 55 | } |
| 56 | |
| 57 | // Parse payload after verification |
| 58 | const payload = JSON.parse(req.body.toString()); |
| 59 | |
| 60 | console.log(`Received ${event} (id: ${webhookId})`); |
| 61 | |
| 62 | // Handle status changes |
| 63 | if (event === 'statusChange') { |
| 64 | console.log(`Agent ${payload.id} status: ${payload.status}`); |
| 65 | |
| 66 | if (payload.status === 'FINISHED') { |
| 67 | console.log(`Summary: ${payload.summary}`); |
| 68 | } else if (payload.status === 'ERROR') { |
| 69 | console.error(`Agent error for ${payload.id}`); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | res.json({ received: true }); |
| 74 | } |
| 75 | ); |
| 76 | ``` |
| 77 | |
| 78 | ### Python Signature Verification (FastAPI) |
| 79 | |
| 80 | ```python |
| 81 | import hmac |
| 82 | import hashlib |
| 83 | from fastapi import Request, HTTPException |
| 84 | |
| 85 | def verify_cursor_webhook(body: bytes, signature_header: str, secret: str) -> bool: |
| 86 | if not signature_header or not secret: |
| 87 | return False |
| 88 | |
| 89 | # Cursor sends: sha256=xxxx |
| 90 | parts = signature_header.split('=') |
| 91 | if len(parts) != 2 or parts[0] != 'sha256': |
| 92 | return False |
| 93 | |
| 94 | signature = parts[1] |
| 95 | expected = hmac.new( |
| 96 | secret.encode(), |
| 97 | body, |
| 98 | hashlib.sha256 |
| 99 | ).hexdigest() |
| 100 | |
| 101 | # Timing-safe comparison |
| 102 | return hmac.compare_digest(signature, expected) |
| 103 | ``` |
| 104 | |
| 105 | ## Common Event Types |
| 106 | |
| 107 | | Event Type | Description | Common Use Cases | |
| 108 | |------------|-------------|------------------| |
| 109 | | `statusChange` | Agent status changed | Monitor agent completion, handle errors | |
| 110 | |
| 111 | ### Event Payload Structure |
| 112 | |
| 113 | ```json |
| 114 | { |
| 115 | "event": "statusChange", |
| 116 | "timestamp": "2024-01-01T12:00:00.000Z", |
| 117 | "id": "agent_123456", |
| 118 | "status": "FINISHED", // or "ERROR" |
| 119 | "source": { |
| 120 | "repository": "https://github.com/user/repo", |
| 121 | "ref": "main" |
| 122 | }, |
| 123 | "target": { |
| 124 | "url": "https://github.com/user/repo/pull/123", |
| 125 | "branchName": "feature-branch", |
| 126 | "prUrl": "https://github.com/user/repo/pull/123" |
| 127 | }, |
| 128 | "summary": "Updated 3 files and fixed linting errors" |
| 129 | } |
| 130 | ``` |
| 131 | |
| 132 | ## Environment Variables |
| 133 | |
| 134 | ```bash |
| 135 | # Your Cursor webhook signing secret |
| 136 | CURSOR_WEBHOOK_SECRET=your_webhook_secret_here |
| 137 | ``` |
| 138 | |
| 139 | ## Local Development |
| 140 | |
| 141 | For local webhook testing, install Hookdeck CLI: |
| 142 | |
| 143 | ```bash |
| 144 | ``` |
| 145 | |
| 146 | Then start the tunnel: |
| 147 | |
| 148 | ```bash |
| 149 | npx hookdeck-cli listen 3000 cursor --path /webhooks/cursor |
| 150 | ``` |
| 151 | |
| 152 | No account required. Provides local tunnel + web UI for inspecting requests. |
| 153 | |
| 154 | ## Resources |
| 155 | |
| 156 | - `overview.md` - What Cursor webhooks are, event types |
| 157 | - `setup.md` - Configure webhooks in Cursor dashboard |
| 158 | - `verification.md` - Signature verification details and gotchas |
| 159 | - `examples/` - Runnable examples per framework |
| 160 | |
| 161 | ## Recommended: webhook-handler-patterns |
| 162 | |
| 163 | For production-ready webhook handling, also use the webhook-handler-patterns skill: |
| 164 | |
| 165 | - [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md) |
| 166 | - [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md) |
| 167 | - [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md) |
| 168 | - [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webh |