Gemini JWKS JWT webhook signatures vs Sume HMAC SHA-256
Gemini dynamic webhooks use asymmetric JWKS-signed JWTs. Sume signs the raw body with a shared-secret HMAC SHA-256 and a timestamp. How each verifies.

Gemini dynamic webhooks emit a JSON Web Token signature that you verify against Google's public keys (JWKS). Sume uses a symmetric scheme instead: it signs the raw JSON body with HMAC SHA-256 over <timestamp>.<raw_body> using your workspace secret. You recompute the HMAC and compare it; there is no key fetch.
Sources: the Gemini Webhooks page and Sume's Webhooks docs, read 2026-10-01.
How does Gemini's dynamic signature check work?
The page says dynamic webhooks use asymmetric public-key JWKS signatures instead of symmetric secrets. Your listener extracts the signature, finds the matching key, and verifies the JWT; its sample errors include "No signature header", "Failed to fetch JWKS" and "Matching key not found". Static webhooks use a stored static signing secret.
What does a Sume verifier check?
Two headers: x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex_signature>. Sign the exact bytes you received, not re-serialized JSON. During secret rotation the header lists one sume-v1= entry per live secret, so accept the delivery when any entry matches.
| Step | Gemini dynamic webhook | Sume webhook |
|---|---|---|
| Key material | Public JWKS keys | Shared workspace secret |
| Signature format | JWT | sume-v1=<hex> HMAC SHA-256 |
| Signed input | Per JWT | <timestamp>.<raw_body> |
| Network call to verify | Fetch the JWKS | None |
How do I stop replayed deliveries?
Reject callbacks whose timestamp is outside your replay tolerance window; the docs call five minutes a reasonable default. Gemini's page also advises validating a timestamp header to reject old payloads.
Do I have to write the verifier myself?
Not in TypeScript: @sume-com/sdk ships verifyWebhook, which checks the sume-v1 signature and the replay window, and is async, so await it. A missing await makes the result an always-truthy promise; see that pitfall. Whatever you use, refuse to verify when the secret is empty.
Sources
Related posts
More in Developers
- Gemini image_size 1k rejected: use 1K, and Sume resolution tiers
Gemini rejects a lowercase image_size such as 1k; use an uppercase K. Sume's images API takes a resolution tier, and only values the model lists.
- Gemini Interactions API image output vs Sume /v1/images
Gemini's Interactions API returns the image on interaction.output_image. Sume's /v1/images returns data[].url, or a 202 job envelope. Check the status code.
- Gemini last_event_id stream resume vs Sume: no SSE, poll status_url
Gemini background interactions can resume a dropped stream from the last event. Sume has no stream to resume: poll status_url and read events_url snapshots.
- Omni 1.1 Flash start and end frame API on Sume
Omni 1.1 Flash can render between two keyframes, including looping clips. On Sume, Omni takes image_url plus end_image_url; frame_images covers other models.
Written by Sume