Trello webhook HMAC-SHA1 vs Sume HMAC-SHA256 in one receiver
Can one server verify Trello and Sume webhooks? Yes, with two verifiers: Trello signs body plus callback URL with SHA-1, Sume signs timestamp.body with SHA-256.

Yes, one service can receive both Trello and Sume webhooks, but it needs two separate verifiers and two routes. The two signatures share nothing except the idea of an HMAC: Trello signs with HMAC-SHA1 over the body plus your callback URL, and Sume signs with HMAC-SHA256 over a timestamp, a dot, and the raw body.
This matters when a Trello card move starts a Sume Format run and the finished video is posted back to the card. Your server then sits between two webhook systems. A verifier written for one will reject every delivery from the other, and a shared helper that quietly skips an empty secret will accept forged traffic from both.
What each side signs
Trello's Webhooks guide says the X-Trello-Webhook header "is a base64 digest of an HMAC-SHA1 hash". The hashed content is the concatenation of the full request body and the callback URL exactly as it was provided when the webhook was created, keyed with the application secret. The page does not describe a timestamp header (read 2026-10-10).
Sume's Run webhooks page describes x-sume-webhook-signature: sume-v1=<hex>, an HMAC-SHA256 over <timestamp>.<raw_body>, a timestamp header you should reject outside a five-minute window, and a fingerprint header that lets you check you hold the right secret without sending it anywhere.
| Property | Trello | Sume run webhook |
|---|---|---|
| Signature header | X-Trello-Webhook | x-sume-webhook-signature |
| Algorithm | HMAC-SHA1, base64 | HMAC-SHA256, hex, prefixed sume-v1= |
| Signed bytes | Body plus the exact callback URL | timestamp.raw_body |
| Replay window | Not described on the page | Reject timestamps outside five minutes |
| Creation check | HEAD request must return 200 | URL must be public HTTPS, checked at create and again at delivery |
| Retries | Three retries after 30, 60 and 120 seconds | Up to 10 attempts, backoff 30 s x 2^(n-1), capped at one hour |
| Dedupe key | Not described on the page | request_id, equal to the run id |
Two routes, two verifiers
Keep the routes separate: /hooks/trello and /hooks/sume. Trello needs the callback URL string in its hash, so the verifier must know the exact URL you registered, including any query string. Sume needs only the secret and the raw bytes. Both verifiers must run on the raw body before any JSON parsing, because a framework that re-serializes the object changes the signed bytes.
The sample below is plain Python with no framework. Both functions refuse an empty secret, and the Sume one accepts a comma-separated header, which Sume sends during a signing-secret rotation (newest entry first).
import base64, hashlib, hmac, time
def verify_sume(raw: bytes, ts: str, sig: str, secret: str, tol: int = 300) -> bool:
if not secret:
return False
try:
t = int(ts)
except (TypeError, ValueError):
return False
if abs(time.time() - t) > tol:
return False
mac = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(e.strip(), f"sume-v1={mac}") for e in (sig or "").split(","))
def verify_trello(raw: bytes, callback_url: str, header: str, app_secret: str) -> bool:
if not app_secret:
return False
mac = hmac.new(app_secret.encode(), raw + callback_url.encode(), hashlib.sha1).digest()
return hmac.compare_digest(base64.b64encode(mac).decode(), header or "")
body, now = b'{"event":"format.run.terminal"}', str(int(time.time()))
good = "sume-v1=" + hmac.new(b"s3cret", now.encode() + b"." + body, hashlib.sha256).hexdigest()
assert verify_sume(body, now, good, "s3cret") and not verify_sume(body, now, good, "")
print("ok")Behaviors that differ in practice
Trello validates the callback URL when you create the webhook by sending a HEAD request, and the webhook is not created unless that returns 200 (read 2026-10-10). A POST-only route fails that check, so the Trello route must answer HEAD. Sume has no such handshake. You can try your Sume route without a real run by calling POST /v1/webhooks/test-deliveries, which sends a signed webhook.test payload to a URL you type.
Trello also disables a webhook automatically after 30 days without a successful delivery combined with more than 1000 consecutive failures, and a single success resets the counts (read 2026-10-10). Sume never changes a run because delivery failed. After ten refused attempts the run is still completed, and you read it from result_url or call POST /v1/format-runs/{run_id}/webhook/redeliver.
Return the Trello result without a second webhook
Answer Trello's event with a 2xx, start the Sume run in the background, and pass your own /hooks/sume URL as communication.webhook_url. Use an Idempotency-Key built from the card id and a version you bump on purpose. If Trello retries the same event after a network error, the second create returns 200 with the original receipt and idempotency_hit: true, not a second paid run.
Branch the Sume handler on outcome, not only status. A degraded outcome means real media exists in artifacts[] but your schema was not satisfied, which is usually worth a human look rather than a silent retry. Keep the card id on your side, keyed by the run id, because input values do not come back in output.
Sources
Related posts
More in Integrations
- How to add an MCP server to ChatGPT with developer mode
Turn on ChatGPT developer mode, create an app for the server's URL, and sign in with OAuth. The steps, with Sume's hosted MCP server as the example.
- How to add subtitles to a video in Python
Add subtitles to a video in Python with Requests: POST the video URL to Sume's /v1/video-captions, poll the job, then read the captioned video_url.
- Add Sume to Claude as a custom connector (remote MCP)
Add Sume's hosted MCP server to Claude under Customize > Connectors, see what Sume's OAuth consent grants, and decide whether to allow paid tools.
- Airflow HTTP sensor: wait for an AI video job to finish
Submit an AI video job with Airflow's HttpOperator, then wait with an HttpSensor in reschedule mode that passes once the job's status is completed.
Written by Sume