Bash and openssl script to verify a Sume webhook signature
A tested bash script that checks a Sume webhook signature with openssl, enforces the 300 s window and exits on an empty secret. Plus what it cannot do.

You can verify a Sume webhook from a shell with openssl dgst: build the string timestamp, dot, raw body, HMAC it with SHA-256 using your signing secret and compare it to the sume-v1= value in x-sume-webhook-signature. The script below does that and exits non-zero on a stale timestamp, a bad signature or an empty secret.
It is useful for replaying a saved delivery while you debug, and for small hooks behind a web server that hands you the body as a file. It is not a good production verifier, for a reason covered below.
The script
Usage is verify.sh BODY_FILE TIMESTAMP SIGNATURE_HEADER with the secret in SUME_WEBHOOK_SECRET. It was tested with a good signature (prints ok), a stale timestamp (exit 1), a bad signature (exit 1) and an empty secret (exit 2).
The awk step takes the last field of the openssl output because different openssl versions print the digest with or without a leading label, such as an algorithm name and an equals sign. Taking the last field works for both. The script checks the timestamp before computing anything, so a stale replay costs nothing to reject. Where you get the three inputs depends on your web server: save the body to a file exactly as received, and copy the two headers x-sume-webhook-timestamp and x-sume-webhook-signature as they came.
#!/usr/bin/env bash
# usage: verify.sh BODY_FILE TIMESTAMP SIGNATURE_HEADER (secret in SUME_WEBHOOK_SECRET)
set -euo pipefail
[ -n "${SUME_WEBHOOK_SECRET:-}" ] || { echo "empty secret" >&2; exit 2; }
body_file=$1 ts=$2 header=$3
now=$(date +%s)
skew=$(( ts > now ? ts - now : now - ts ))
(( skew <= 300 )) || { echo "stale timestamp" >&2; exit 1; }
digest=$({ printf '%s.' "$ts"; cat "$body_file"; } \
| openssl dgst -sha256 -hmac "$SUME_WEBHOOK_SECRET" -hex | awk '{print $NF}')
IFS=',' read -ra entries <<< "$header"
for e in "${entries[@]}"; do
[ "${e// /}" = "sume-v1=$digest" ] && { echo ok; exit 0; }
done
echo "bad signature" >&2; exit 1Exit codes
The codes let a caller tell a configuration error from a rejected request.
| Exit | Meaning |
|---|---|
| 0 | Signature matches, timestamp within 300 s |
| 1 | Stale timestamp or no matching entry |
| 2 | SUME_WEBHOOK_SECRET is empty |
Tradeoffs
The string comparison in bash is not constant time, so do not expose this to untrusted network timing. Use it for offline checks and CI fixtures, and use your language's HMAC compare in the real receiver. Also keep the body byte for byte: command substitution strips trailing newlines, which is why the script streams the file with cat instead of capturing it.
For a quick local test, save a delivery body to body.json, compute its signature with the same openssl command, and pass both to the script. Changing one byte of the file should flip the result to bad signature, which is a fast way to confirm that you are hashing the bytes you think you are.
Sources
Related posts
More in Developers
- Fade out the end of a Short: Timeline fade_out_seconds limits
Sume Timeline fades video and audio at the ends with output.fade_in_seconds and fade_out_seconds, 0 to 5 each, summing to at most the length. Setup for Shorts.
- Verify a Sume webhook signature in Perl with Digest::SHA
A tested Perl verifier for the Sume x-sume-webhook-signature header using Digest::SHA, an XOR compare, the 300 s window and an empty-secret refusal.
- Pin the image model id on the queue row so a swap can't break old jobs
Store the Sume model id on each queued render row at enqueue time, then re-point only rows still carrying a retired id. Python sqlite3 sample for gpt-image-1.
- Polling a Sume job for 20 minutes: 600 fixed reads or 44 backed off
Counting reads for a 20-minute Sume job poll: fixed 2 s versus a 1 s schedule doubling to 30 s. Arithmetic you can run, and why the server hint still wins.
Written by Sume