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.

4 min readSume
All posts

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.

Trello webhook rules (Trello Webhooks guide, read 2026-10-10) next to Sume run webhooks (docs.sume.com, read 2026-10-10)
PropertyTrelloSume run webhook
Signature headerX-Trello-Webhookx-sume-webhook-signature
AlgorithmHMAC-SHA1, base64HMAC-SHA256, hex, prefixed sume-v1=
Signed bytesBody plus the exact callback URLtimestamp.raw_body
Replay windowNot described on the pageReject timestamps outside five minutes
Creation checkHEAD request must return 200URL must be public HTTPS, checked at create and again at delivery
RetriesThree retries after 30, 60 and 120 secondsUp to 10 attempts, backoff 30 s x 2^(n-1), capped at one hour
Dedupe keyNot described on the pagerequest_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

All Integrations posts

Written by Sume