Python Workers ASGI: a FastAPI Sume webhook receiver

Python Workers now accept ASGI frameworks. A FastAPI Sume receiver keeps the same rules there: raw body, constant-time HMAC compare, replay window, fast 2xx.

4 min readSume
All posts

Yes, you can run a FastAPI Sume receiver on Cloudflare Python Workers: since September 2, 2026 any WSGI or ASGI framework can be used there, through wsgi or asgi from the workers module. The verification logic does not change. Read the raw body, check sume-v1 in constant time, enforce the replay window, return 2xx fast.

Cloudflare facts are from two changelog entries; Sume facts from the Format cookbook and Run webhooks, read 2026-09-30. The changelog snippets I read do not show wiring code, so follow Cloudflare's page for the adapter call.

What changed on Python Workers?

Frameworks following WSGI or ASGI can be used in Python Workers: choose wsgi for Django or Flask and asgi for ASGI frameworks such as FastAPI. A separate September 8, 2026 change makes Python 3.14 the default for new Python Workers with compatibility date 2026-09-08 or later.

What stays the same in the receiver?

Receiver rules from the Sume docs, read 2026-09-30: https://docs.sume.com/formats/cookbook
RuleSume docs
BodyRead the raw body before any JSON parsing
Signed string<timestamp>.<raw_body>, HMAC SHA-256, hex
Headerx-sume-webhook-signature: sume-v1=<hex_signature>
Comparehmac.compare_digest
Replay windowCookbook uses TOLERANCE_SECONDS = 300
Response2xx quickly, after durably recording the event

What changes on a Worker?

Where the secret comes from. The cookbook reads os.environ["SUME_COM_WEBHOOK_SIGNING_SECRET"] at import time; on a Worker, supply the secret through whatever binding mechanism Cloudflare documents, and keep the name SUME_COM_WEBHOOK_SIGNING_SECRET. Refuse to verify when it is empty. The function below is the cookbook's check with that guard added.

import hashlib
import hmac
import time


def verify(secret: bytes, raw: bytes, timestamp: str | None, signature: str | None) -> bool:
    if not secret or not timestamp or not signature:
        return False
    try:
        ts = int(timestamp)
    except ValueError:
        return False
    if abs(time.time() - ts) > 300:
        return False
    digest = hmac.new(secret, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sume-v1={digest}", signature)

How should the handler respond?

Verify, record the event, return 2xx. Each attempt times out at 10s, and a slow endpoint burns the attempt budget and gets retried. Dedupe on request_id for runs or job_id for jobs. The server-side equivalent on regular hosts is in Python webhook receiver: FastAPI and Django. During a secret rotation the header can carry several comma-separated sume-v1= entries, so split on commas in production; secret rotation shows how.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume