Cursor Security Review bot on a Sume webhook handler: what to find
Cursor added a Security Review bot on Sep 23. A webhook handler for Sume should pass seven checks: raw body, timestamp window, rotation, empty secret and more.

A review bot checking a Sume webhook handler should confirm seven things: raw-body signing, a timestamp window, rotation-safe comparison, a refused empty secret, job_id idempotency, a fast 2xx after durable storage, and no trust in unverified events. The Cursor changelog lists Rollouts and Security Review bots on Sep 23, available on Teams and Enterprise plans.
Use this list as the brief when an agent writes the handler and a reviewer, human or bot, checks it.
The seven checks
| Check | What the docs say |
|---|---|
| Sign over the raw body | HMAC SHA-256 over <timestamp>.<raw_body>; a re-serialized body will not match. |
| Replay window | Reject timestamps outside your tolerance; five minutes is a reasonable default. |
| Rotation | During rotation the header carries one sume-v1= entry per live secret; accept when any matches. |
| Secret present | The secret is yours, read from the dashboard or GET /v1/webhooks/signing-secret. |
| Idempotency | Use job_id as the key; run webhooks dedupe on request_id. |
| Respond fast | Return 2xx after durably storing; attempts time out after 10 seconds. |
| Keep a fallback | Ten failed attempts leave a failed delivery, so keep status polling. |
A verifier that passes
This Python function refuses an empty secret, checks the timestamp window, and compares every sume-v1= entry in constant time.
import hashlib
import hmac
import time
def verify_sume_webhook(raw_body: bytes, timestamp: str, signature_header: str,
secret: str, tolerance_seconds: int = 300) -> bool:
if not secret:
raise ValueError("webhook signing secret is empty")
try:
ts = int(timestamp)
except ValueError:
return False
if abs(int(time.time()) - ts) > tolerance_seconds:
return False
digest = hmac.new(
secret.encode(), str(ts).encode() + b"." + raw_body, hashlib.sha256
).hexdigest()
expected = ("sume-v1=" + digest).encode()
matched = False
for entry in signature_header.split(","):
if hmac.compare_digest(entry.strip().encode(), expected):
matched = True
return matched
The easy mistakes
Review bots tend to flag the generic problems. These are the ones specific to this signature scheme.
- Parsing the JSON and signing
JSON.stringifyof it. Sign the bytes you received. - Stopping at the first non-matching entry during rotation, which rejects a valid delivery.
- Using a plain
==on the signature rather than a constant-time comparison. - Reading the secret with a default of an empty string, so an unset variable silently disables verification.
- Treating a redelivery as new work. Redeliver sends a fresh timestamp and signature for the same job.
Idempotency and retries
Sume retries network errors and non-2xx responses up to 10 attempts in total, with a fixed delay (30 seconds by default) rather than exponential backoff. The per-attempt timeout is 10 seconds. A handler that does slow work before replying burns its own budget.
Store the event first, answer 2xx, and process afterwards. Use job_id as the key so a retry or a manual redeliver does not repeat the side effect.
Test without a real job
The dashboard Send test action posts a dummy signed webhook.test payload to a URL you type, and it never replays a real job. Use it to prove the signature path. Redeliver, by contrast, re-sends a real job's terminal event, so use it to test the idempotency path.
Sources
Related posts
More in Developers
- Cursor self-hosted machines can't take Sume webhooks on a private URL
Sume rejects localhost, private-network and non-HTTPS webhook URLs. An agent on a self-hosted machine should poll status_url or use a public HTTPS receiver.
- D-ID API uses Basic auth; Sume uses a bearer API key
D-ID's API docs say requests use the Authorization header with Basic credentials. Sume sends Authorization: Bearer with an API key. A short migration checklist.
- Dagster asset retries and Sume: stop a retry from billing twice
A Dagster RetryPolicy re-runs the whole asset function. Give the Sume submit a stable Idempotency-Key and fail terminal jobs with allow_retries=False.
- Dart Shelf: verify a Sume webhook with package:crypto HMAC-SHA256
A Dart Shelf server that reads the raw body, checks the sume-v1 HMAC with package:crypto in constant time and exits at boot when the signing secret is empty.
Written by Sume