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.

5 min readSume
All posts

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

Webhook endpoints and required scopes, as of 2026-10-08
CallScopeWhat it does
GET /v1/webhooks/signing-secretaccount:readReturns your workspace signing secret
POST /v1/webhooks/signing-secret/rotateaccount:writeStarts a 24-hour window with two live secrets
POST /v1/webhooks/test-deliveriesaccount:writeSends a dummy signed webhook.test to a URL you type
POST /v1/jobs/{job_id}/webhook/redeliverjobs:writeRe-POSTs the real terminal job event with a fresh timestamp and signature
POST /v1/format-runs/{run_id}/webhook/redeliverformats:writeSame 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

All Developers posts

Written by Sume