Python 3.15 UTF-8 default: still verify Sume webhooks on raw bytes

Python 3.15 makes UTF-8 the default for open() without an encoding. It does not change that a Sume signature covers raw body bytes, so verify before any parse.

5 min readSume
All posts

Python 3.15's UTF-8 default does not change how a Sume webhook is verified: Sume signs the raw JSON body with HMAC-SHA256 over <timestamp>.<raw_body>, so your Python handler must compute the digest over the exact bytes that arrived, before any JSON parse or re-serialize. What 3.15 does change is the hidden-encoding case around it, such as reading a stored secret file or writing a received event to disk with open() and no encoding.

The Python facts come from the What's New in Python 3.15 page, which was still labelled a release candidate and a draft when I read it on 2026-10-03; confirm the final release notes before you rely on it. The Sume facts come from Webhooks, Run webhooks and Verifying webhooks. I did not run a receiver on 3.15.

What exactly changes in Python 3.15?

Per the release notes (PEP 686), Python uses UTF-8 as the default encoding independent of the system environment, so I/O without an explicit encoding argument uses UTF-8. It applies only when no encoding is given, can be turned off with PYTHONUTF8=0 or -X utf8=0, and the notes still advise passing encoding explicitly for compatibility between versions. encoding='locale' selects the locale encoding and has been supported since 3.10.

Where an encoding choice touches a Sume webhook receiver. Python behavior from the 3.15 release candidate notes, read 2026-10-03.
StepEncoding matters?What to do
Computing the HMACNo, it is bytesUse the raw request body bytes as received
Building the signed messageOnly if you decode firstJoin timestamp, a period and the raw bytes; do not decode and re-encode
Reading the signing secret from a fileYes, if you call open() without encodingPass encoding='utf-8' explicitly
Writing the event to disk before replyingYes, same reasonWrite the raw bytes, or pass encoding='utf-8'
Parsing JSON after verificationNo, parse the verified bytesParse once, after the signature passes

What must the verifier still do?

Reject a timestamp outside your replay window, five minutes by default in Sume's docs, and accept the delivery when any sume-v1= entry in x-sume-webhook-signature matches: during a secret rotation the header carries one entry per live secret, newest first, comma separated. Compare with hmac.compare_digest.

Then return any 2xx after durably recording the event. A job webhook has a 10-second timeout per attempt and a fixed 30-second default gap; a run webhook has the same 10-second timeout with exponential backoff capped at one hour. Dedupe on job_id for job events and request_id for run events, and keep status_url polling as the backup.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume