Postgres 17.11 pgcrypto change: verify a Sume webhook with hmac()

The Postgres minor release changed legacy pgcrypto ciphers. Its listed changes never mention hmac(), so a Sume sume-v1 signature check in SQL still works.

5 min readSume
All posts

Yes, you can check a Sume signature in SQL, and the pgcrypto change in this release does not touch hmac(). The Supabase changelog's pgcrypto item concerns legacy ciphers, and it never mentions hmac, so I would still test after upgrading rather than assume.

Supabase and PostgreSQL facts are from the vendor pages; Sume facts from Verifying webhooks and Job webhooks, read 2026-10-01.

What did the Postgres minor release change?

The Supabase changelog covers PostgreSQL 15.19 and 17.11, available from September 28 and fixing 44 CVEs. Its breaking changes are ltree index corruption, a pgcrypto legacy-cipher change, a btree_gist NaN float change and restrictions on custom operator estimators. For pgcrypto it names blowfish, cast5 and bf, where decryption now fails by default.

Breaking changes in the Supabase changelog, read 2026-10-01
AreaChange
ltreeIndex corruption fix
pgcryptoLegacy ciphers (blowfish, cast5, bf): decryption fails by default
btree_gistNaN float handling
Custom operatorsEstimator restrictions

What signature does Sume send?

HMAC-SHA256 over <timestamp>.<raw_body>, sent as x-sume-webhook-signature: sume-v1=<hex> with x-sume-webhook-timestamp. During a secret rotation there are comma-separated entries for 24 hours and any match is accepted. The default replay window is 300 seconds. PostgreSQL's pgcrypto has hmac(data text, key text, type text) returning bytea, and encode(..., 'hex') gives hex.

What does the function look like?

I ran this on Postgres 17 with pgcrypto installed in the extensions schema, as on Supabase; qualify hmac to match your schema. It returns false on an empty secret, a bad timestamp, a stale one or a mismatch, and handles two entries. It must be stable because it reads now(). The = comparison is not constant-time, which matters little over an HMAC but is why the SDK verifier is preferable where you can use it.

create or replace function public.verify_sume_webhook(
  body text, ts text, sig_header text, secret text
) returns boolean
language plpgsql stable as $$
declare
  entry text;
  expected text;
begin
  if secret is null or secret = '' or ts is null or sig_header is null
     or ts !~ '^[0-9]{1,12}$' then
    return false;
  end if;
  if abs(extract(epoch from now()) - ts::bigint) > 300 then
    return false;
  end if;
  expected := 'sume-v1=' || encode(
    extensions.hmac(ts || '.' || body, secret, 'sha256'), 'hex');
  foreach entry in array string_to_array(replace(sig_header, ' ', ''), ',') loop
    if entry = expected then
      return true;
    end if;
  end loop;
  return false;
end
$$;

Where would you call it?

From a function that receives the raw body unparsed, passing the secret from its environment, not from a table you log. Do not parse and re-serialize the JSON first. For the usual route see the Supabase middleware post.

Does the upgrade need other checks?

Run a signed test with POST /v1/webhooks/test-deliveries after the upgrade, and keep status polling as a backup, since the docs call delivery a convenience.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume