Verify a Sume agent.run.terminal webhook in Python
Python HMAC-SHA256 check for a Sume run webhook: sume-v1 signature over timestamp.raw_body, a five-minute window, and a verifier that refuses an empty secret.

To verify a Sume run webhook in Python, compute HMAC-SHA256 over <timestamp>.<raw_body> with your signing secret, prefix the hex digest with sume-v1=, and compare it in constant time to the x-sume-webhook-signature header. Refuse an empty secret, and reject timestamps outside a five-minute window.
What Sume sends
The run webhook docs list the headers: x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex_signature>. An Agent Completion delivers the event agent.run.terminal with object agent.run. The check runs against the raw body, before any JSON parse or re-serialize.
The verifier
This function mirrors the scheme in the docs. It returns False for an empty secret, so a missing environment variable fails closed instead of accepting everything. Read the secret from SUME_COM_WEBHOOK_SIGNING_SECRET.
import hashlib, hmac, os, time
def verify(raw_body: bytes, timestamp: str, signature: str,
secret: str, tolerance: int = 300) -> bool:
if not secret: # fail closed on an unset env var
return False
try:
ts = int(timestamp)
except ValueError:
return False
if abs(int(time.time()) - ts) > tolerance:
return False
digest = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sume-v1={digest}", signature)
if __name__ == "__main__":
secret = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")
body, ts = b'{"event":"agent.run.terminal"}', str(int(time.time()))
sig = "sume-v1=" + hmac.new(b"test", f"{ts}.".encode() + body,
hashlib.sha256).hexdigest()
print(verify(body, ts, sig, "test"), verify(body, ts, sig, secret))Why the empty-secret guard matters
A signature made with an empty key is a valid HMAC. If your process starts without the variable and your code does hmac.new(b'', ...), an attacker who knows that can sign forged events. The guard turns that misconfiguration into a rejected request. The last line of the sample prints True for the test secret and False when the environment variable is unset.
After the check
The points that matter here, in the order you will hit them:
- Record the event durably, then return a 2xx quickly. Sume times out each attempt at 10 seconds.
- Dedupe on the envelope
request_id, which equalsrun_idand is stable across retries. - Branch on
outcome(ok,degraded,error), not onstatusalone. - Get the secret from the Webhooks tab of the dashboard or
GET /v1/webhooks/signing-secret.
Using the SDK instead
If your receiver is TypeScript, `verifyWebhook` in `@sume-com/sdk` does this check for you. The Python function above exists for receivers in other languages. Both use the same scheme, as the run-webhook page notes.
Testing the receiver
Use the Send test control on the Webhooks page of the dashboard, or POST /v1/webhooks/test-deliveries, which needs account:write. It sends a dummy webhook.test payload. It is not a replay of a real run, so it proves your signature check and your secret, not your run handling. If a signature fails, compare the x-sume-webhook-secret-fingerprint header with the fingerprint shown next to the secret in the dashboard. The fingerprint is the only part of this data that is safe to paste into a ticket. Never log the secret itself, and rotate it if it might have leaked.
Sources
Related posts
More in Developers
- Verify a Sume image webhook in Python, then read artifacts[]
A 23-line Python verifier for Sume's x-sume-webhook-signature header on an image job: refuses an empty secret, checks a 5-minute window, reads the image URL.
- Verify the Sume signature in the HTTP handler, not in the queue worker
A queue delay over 300 seconds makes a valid Sume signature look stale. Verify at receipt, enqueue the verified event, and use redeliver if a late check failed.
- Verify x-sume-webhook-signature in Node: sume-v1 HMAC, raw body
A node:crypto verifier for Sume's sume-v1 signature that refuses an empty secret, checks the 5-minute window, and accepts either entry during a rotation.
- verifyWebhook toleranceSeconds: 300 by default, and 0 turns replay off
In the Sume SDK, verifyWebhook rejects deliveries older than 300 seconds by default. toleranceSeconds 0 skips that check. When each setting is right.
Written by Sume