fal webhook ED25519 and JWKS vs Sume's HMAC-SHA256 check
fal signs webhooks with ED25519 keys fetched from a JWKS URL. Sume signs HMAC-SHA256 over timestamp.body with a workspace secret. The two checks, side by side.

A fal webhook is verified with public keys: you fetch a JSON Web Key Set from https://rest.fal.ai/.well-known/jwks.json and check an ED25519 signature over a message built from four headers and a SHA-256 of the body. A Sume webhook is verified with a shared secret: you compute HMAC-SHA256 over <timestamp>.<raw_body> and compare it with x-sume-webhook-signature. A fal verifier cannot be reused for Sume, and the reverse is also true.
The fal steps are from its Webhooks page, read on 2026-10-02. The Sume steps are from Webhooks and Run webhooks.
How does fal verify a webhook?
Four headers must be present or the request is invalid: X-Fal-Webhook-Request-Id, X-Fal-Webhook-User-Id, X-Fal-Webhook-Timestamp (Unix seconds) and X-Fal-Webhook-Signature (hex). The timestamp must be within plus or minus 300 seconds. The message is the request id, user id, timestamp and the hex SHA-256 of the raw body, joined by newlines and encoded as UTF-8.
The signature is checked against each key in the JWKS, where each key's x field is a base64url ED25519 public key. If any key verifies, the request is valid. fal says the JWKS may be cached but not longer than 24 hours because keys can change.
How does Sume verify a webhook?
Sume signs the raw JSON body with HMAC SHA-256 over the timestamp, a dot, and the raw body. The headers are x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. The docs call five minutes a reasonable replay window. During a secret rotation the signature header carries one sume-v1= entry per live secret, newest first, separated by commas, and you accept the delivery if any entry matches.
The secret is yours: read it on the dashboard Webhooks tab or from GET /v1/webhooks/signing-secret with an API key carrying account:read. Every delivery also carries x-sume-webhook-secret-fingerprint, so you can compare fingerprints without sending the secret anywhere.
What are the differences in one table?
Verify against the raw bytes in both cases, before any JSON parse.
| Item | fal | Sume |
|---|---|---|
| Key type | ED25519 public keys from a JWKS URL | One HMAC-SHA256 secret per workspace |
| Signed message | Request id, user id, timestamp, SHA-256 of body, newline-joined | <timestamp>.<raw_body> |
| Headers | X-Fal-Webhook-Request-Id, -User-Id, -Timestamp, -Signature | x-sume-webhook-timestamp, x-sume-webhook-signature |
| Replay window | Plus or minus 300 seconds | Five minutes suggested |
| Key refresh | Cache JWKS up to 24 hours | Rotation: several sume-v1= entries in one header |
What does a Sume verifier look like in Python?
This follows the scheme in Sume's docs and refuses an empty secret. Pass the raw request bytes, not re-serialized JSON.
import hashlib
import hmac
import time
def verify_sume_webhook(raw_body: bytes, timestamp: str, signature_header: str,
secret: str, tolerance: int = 300) -> bool:
if not secret:
raise ValueError("webhook signing secret is empty")
try:
ts = int(timestamp)
except ValueError:
return False
if abs(time.time() - ts) > tolerance:
return False
digest = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body,
hashlib.sha256).hexdigest()
expected = f"sume-v1={digest}".encode()
matched = False
for entry in signature_header.split(","):
if hmac.compare_digest(entry.strip().encode(), expected):
matched = True
return matched
Sources
Related posts
More in Comparisons
- fal webhook retries: 31 attempts, 15 s timeout, vs Sume's 10
fal retries a failed webhook with backoff up to 31 times while the stored result lasts; Sume makes up to 10 attempts with a 10 second timeout. What to build.
- Fastest AI image model API: what the October 2026 claims say
Flare says half the latency of GPT Image 2, MAI-Image-2.6-Flash says 2.8x faster than GPT-Image-2-Medium. None are comparable. A timing script for Sume models.
- FFmpeg whisper filter vs a hosted transcript call for video
FFmpeg's whisper filter needs whisper.cpp and a model file you manage. Sume's video-inspect transcribe returns words and sentence segments for $0.01 a minute.
- FFmpeg xfade has 59 transitions: which does Sume Timeline take?
FFmpeg's xfade page lists 59 transition values, including custom. Sume Timeline 1.0 accepts six: fade, wipeleft, wiperight, slideup, slidedown and dissolve.
Written by Sume