Get the Sume webhook secret with GET /v1/webhooks/signing-secret
Read the workspace webhook secret with an account:read key and load it as SUME_COM_WEBHOOK_SIGNING_SECRET without printing it. A Python deploy step.

Sume webhooks are signed with a secret that is derived for your workspace. It is not a shared platform value. You can reveal it on the Webhooks tab of the dashboard, or read it from GET /v1/webhooks/signing-secret with an API key that has the account:read scope. The docs say to store it as SUME_COM_WEBHOOK_SIGNING_SECRET, the same name that the delivery worker uses.
When the API route helps
A dashboard copy is fine for one service. The API route fits a deploy script that provisions several receivers, or a rotation check that must run unattended. Job webhooks and run webhooks share this one secret, so a single receiver setting covers both.
Scopes
A key without account:read receives a 403. Create a separate key with that scope for the deploy step, so that the key your application runs with can be narrower. Keep the secret out of logs and build output. The fingerprint is the safe thing to print, since it identifies a secret without revealing it.
| Place | What it shows |
|---|---|
| Dashboard Webhooks tab (Reveal) | The secret and its fingerprint |
| GET /v1/webhooks/signing-secret | The secret (needs account:read) |
| x-sume-webhook-secret-fingerprint header | The fingerprint of the secret that signed the delivery |
| webhook_delivery.signing_secret_fingerprint | The same fingerprint on the receipt |
Deploy step
The script writes the secret to a file that the receiver loads, with owner-only permissions, and prints nothing sensitive. The response is an object named data with scope, version, fingerprint and secret, and the secret is 64 hex characters.
import json, os, stat, sys, urllib.request
req = urllib.request.Request("https://api.sume.com/v1/webhooks/signing-secret",
headers={"x-api-key": os.environ["SUME_ADMIN_KEY"]})
with urllib.request.urlopen(req, timeout=15) as resp:
body = json.load(resp)
secret = body["data"]["secret"]
if len(secret) < 32:
sys.exit("unexpected signing secret length")
path = "/etc/myapp/sume-webhook.env"
with open(path, "w") as f:
f.write(f"SUME_COM_WEBHOOK_SIGNING_SECRET={secret}\n")
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
print("wrote", path, "fingerprint", body["data"]["fingerprint"])After rotation
During a rotation the signature header carries one entry for each live secret, newest first. Accept the delivery if any sume-v1= entry matches, and refresh the stored secret once the old one is retired.
Sources
Related posts
More in Developers
- GET /v1/jobs 400 unknown_parameter: a typo'd filter no longer widens
A misspelled query key on GET /v1/jobs, such as state for status, now returns 400 with a suggestion instead of a full unfiltered page. Handle it in TypeScript.
- Sume job status headers: cache-control no-store and x-sume-poll-after
GET /v1/jobs/:id/status is never cacheable and sends x-sume-poll-after: 2. What each header means for CDNs, browsers and a TypeScript poll loop.
- Cold Sume API key burst: first requests get the Free 120-write floor
The API reads your plan after auth, so a first-seen key is judged at the Free 120 write floor. Warm a Pro or Scale key with GET /v1/me before a burst.
- Sume ratelimit-limit: read the budget from the header, not a table
Plan numbers are 120 to 1200 writes a minute, but dev and self-hosted deployments can differ. Calibrate a Python client from ratelimit-limit at startup.
Written by Sume