Go 1.27.2 and 1.26.9 patch net/http: update your Sume webhook receiver

Go 1.27.2 and 1.26.9 (2026-10-08) list security fixes in net/http and crypto/tls. A stdlib receiver for Sume webhooks that verifies the signature, in two files.

4 min readSume
All posts

If a Go service receives Sume webhooks, rebuild it on Go 1.27.2 (or 1.26.9 if you stay on the 1.26 line). Both were released on 2026-10-08, and the Go release history lists security fixes to net/http, crypto/tls, net/textproto, html/template, and os in each. A public webhook endpoint is a net/http server on the open internet, so it is a service that should take the patch first.

What the release history says

The Go release history (read 2026-10-10) gives only package names for the security fixes and points to the milestone on the Go issue tracker for detail. I did not follow that to individual advisories, so this post makes no claim about what the bugs are or whether a webhook receiver is exposed to them.

Go release history entries, read 2026-10-10
ReleaseDateSecurity fixes listed inAlso in the entry
go1.27.22026-10-08go command, crypto/tls, html/template, net/http, net/textproto, osBug fixes to net/http, os, encoding/json, encoding/json/v2, crypto/mlkem
go1.26.92026-10-08go command, crypto/tls, html/template, net/http, net/textproto, osBug fixes to net/http, os, crypto/mlkem
go1.27.12026-09-01None listed for these packagesBug fixes to net/http, os, encoding/json

A receiver that checks the signature

The webhooks page says Sume signs <timestamp>.<raw_body> with HMAC SHA-256 and sends x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. During a secret rotation the signature header holds one sume-v1= entry per live secret, comma separated, and a delivery is valid if any entry matches. The first file is the verifier.

It rejects an empty secret, a timestamp older than five minutes in either direction, and any entry that does not match, and it compares every entry so timing does not reveal which one matched.

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

func verify(secret string, raw []byte, ts, header string) bool {
	n, err := strconv.ParseInt(ts, 10, 64)
	if err != nil || secret == "" || time.Since(time.Unix(n, 0)).Abs() > 5*time.Minute {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(ts + "."))
	mac.Write(raw)
	want := []byte("sume-v1=" + hex.EncodeToString(mac.Sum(nil)))
	ok := false
	for _, e := range strings.Split(header, ",") {
		if hmac.Equal([]byte(strings.TrimSpace(e)), want) {
			ok = true // keep looping: rotation sends one entry per live secret
		}
	}
	return ok
}

The second file is the handler. It reads the raw body with a 1 MiB cap before it checks anything, verifies, and answers 204. Replace the comment with a durable write keyed by job_id, because the docs treat job_id as the idempotency key and say Sume retries up to 10 times.

package main

import (
	"io"
	"log"
	"net/http"
	"os"
)

func main() {
	secret := os.Getenv("SUME_COM_WEBHOOK_SIGNING_SECRET")
	http.HandleFunc("/sume/webhook", func(w http.ResponseWriter, r *http.Request) {
		raw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
		ts, sig := r.Header.Get("x-sume-webhook-timestamp"), r.Header.Get("x-sume-webhook-signature")
		if err != nil || !verify(secret, raw, ts, sig) {
			http.Error(w, "bad signature", http.StatusUnauthorized)
			return
		}
		w.WriteHeader(http.StatusNoContent) // store job_id durably first, then 2xx
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Run and pin it

I vetted both files and ran them on Go 1.27.1 with a signed test request. A request carrying sume-v1=bad,sume-v1=<good> returned 204, and one with only a bad entry returned 401. Set go 1.27.2 in go.mod, or run go get go@1.27.2 if your module should not build with anything older, then rebuild and redeploy the container.

  • The receiver has one job: verify, store, answer. Do the work that calls other services after you answer.
  • Sume allows each attempt 10 seconds, so a handler that does slow work counts against the retry budget.
  • Keep polling GET /v1/jobs/:id/status as a backup for events that never arrive.
  • Rebuild every Go binary that talks to Sume, not only the receiver. The client side also links net/http and crypto/tls.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume