Which key scope each Sume webhook endpoint needs
Reading the signing secret needs account:read, rotating and Send test need account:write, and redelivering a job needs jobs:write. A least-privilege map.

Four Sume webhook calls need three scopes. GET /v1/webhooks/signing-secret needs account:read. POST /v1/webhooks/signing-secret/rotate and POST /v1/webhooks/test-deliveries need account:write. POST /v1/jobs/{job_id}/webhook/redeliver needs jobs:write. Your receiver itself needs no API key at all, because it only holds the signing secret and checks signatures.
The map
| Call | Scope | What it does |
|---|---|---|
| GET /v1/webhooks/signing-secret | account:read | Returns your workspace signing secret |
| POST /v1/webhooks/signing-secret/rotate | account:write | Starts a 24-hour window with two live secrets |
| POST /v1/webhooks/test-deliveries | account:write | Sends a dummy signed webhook.test to a URL you type |
| POST /v1/jobs/{job_id}/webhook/redeliver | jobs:write | Re-POSTs the real terminal job event with a fresh timestamp and signature |
| POST /v1/format-runs/{run_id}/webhook/redeliver | formats:write | Same idea for a Format run receipt |
Split keys by job
The secret is derived for your workspace and shared by job webhooks and run webhooks, so one verifier covers both. Treat it like the API key. The scopes let you split duties:
- Receiver: no API key. Only
SUME_COM_WEBHOOK_SIGNING_SECRET. - Deploy tooling that provisions the secret: a key with only
account:read. - Rotation runbook: a separate key with
account:write, used by a person or a protected pipeline. - Recovery job that redelivers missed events: a key with
jobs:write, and read access if it also polls.
A least-privilege fetch
The script reads the secret with a read-only key and refuses to continue on a 403. It never prints the secret. It prints a short digest so you can compare against the fingerprint in the dashboard without exposing the value. The fingerprint format is not specified in the docs here, so compare it by eye or in the dashboard rather than in code.
import hashlib, os
import httpx
key = os.environ.get("SUME_READ_KEY", "")
if not key:
raise SystemExit("set SUME_READ_KEY (a key with account:read)")
r = httpx.get(
"https://api.sume.com/v1/webhooks/signing-secret",
headers={"Authorization": f"Bearer {key}"},
timeout=20,
)
if r.status_code == 403:
raise SystemExit("key lacks account:read")
r.raise_for_status()
body = r.json()
secret = body.get("secret") or body.get("signing_secret") or ""
if not secret:
raise SystemExit("no secret field found in response")
print("length", len(secret), "sha256", hashlib.sha256(secret.encode()).hexdigest()[:8])Failure modes
A 403 on rotate with a read-only key is the scope check working. Give the rotation runbook its own key rather than widening the read key.
Why a receiver needs no key
The signing secret is derived for your workspace, so a valid signature proves that Sume signed the delivery for you, not for any holder of a shared platform secret. The receiver needs only that secret and a clock. Keeping API keys off the public endpoint means a bug in the receiver cannot leak a key that can spend money.
Store the signing secret with the same care as an API key, in a secret manager, and read it into the process at start. Refuse to boot with an empty value.
POST /v1/webhooks/test-deliveries sends a dummy webhook.test event to a URL you give it. It never replays a real job, and the body has no job_id. Redeliver is the call that replays a real terminal event, and it works after all ten automatic attempts are used without spending one of them. Use the first to check wiring and the second to recover a missed job. Do not use one for the other.
Sources
Related posts
More in Developers
- Sume /v1/videos has id and generation_id: which one do you store?
Both fields hold the same job id on Sume, so store id. Why generation_id exists and where that id also works: /v1/jobs status and result, webhooks, redeliver.
- Which languages? Sume's language fields vs MAI's 23 and 60 counts
Microsoft states 23 languages for MAI-Voice-2.1 and 60 for MAI-Transcribe-2-Streaming. Sume publishes no count; here are its language fields.
- Which request_id do you dedupe a Sume run webhook on?
Dedupe on the envelope request_id, which equals run_id and is stable across retries. The nested payload.request_id is a different id. A code check.
- Which Sume API errors to retry and which to stop on: Node wrapper
A retry policy by error.code for Sume submits: retry rate_limited, queue_full, provider_capacity_exceeded; stop on 400, 401, 402, 409. Node wrapper with jitter.
Written by Sume