Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneeshammz

Relationships

#2204 Add a static-token webhook scheme (constant-time header equality) for Telegram-style secret tokens

Opened by magistr · 9/16/2026· Shipped 9/17/2026

Problem statement

swamp serve webhook endpoints authenticate with a closed, HMAC-over-body scheme set: github, jira, linear, stripe, slack, generic (src/serve/webhook_verifiers.ts, #716). Every scheme — generic included — routes through prefixedBodyVerifier, which computes HMAC-SHA256(body, secret) and constant-time-compares it to a header digest. generic only makes the header name and prefix configurable; it still requires an HMAC of the body.

Some common webhook sources don't sign the body at all — they send a static shared secret in a header. The Telegram Bot API is the prominent one: setWebhook registers a caller-chosen secret token, after which Telegram sends that same token verbatim in the X-Telegram-Bot-Api-Secret-Token request header on every update, with no body HMAC. No current scheme can verify this, so Telegram (and any static-token source) cannot call a serve webhook directly — it requires an external proxy that re-signs each request with an HMAC serve accepts.

Verified on latest 20260916.191155.0-sha.c30806e5: WEBHOOK_SCHEMES = [github, jira, linear, stripe, slack, generic]; webhookVerifierFor(generic)prefixedBodyVerifier(header, prefix).

Proposed solution

Add a static (a.k.a. token) scheme alongside generic:

  • Config: { scheme: "static", header: string, prefix?: string }.
  • Verify: read header, strip optional prefix, constantTimeEqual(value, secret). No body HMAC, no timestamp.
  • Reuse the existing constantTimeEqual primitive; register it in WEBHOOK_SCHEMES and webhookVerifierFor; expose via the --webhook …:<scheme>:<header>:<prefix> tail and the serve.yaml scheme: field.

This lets Telegram (header: x-telegram-bot-api-secret-token) and any static-secret webhook authenticate directly, with no re-signing proxy in front of serve.

Alternatives considered

  • generic with an HMAC — impossible; these sources don't compute one over the body.
  • Front serve with a proxy that re-signs each request into an accepted HMAC scheme — works, but adds a permanent extra service purely to bridge the auth mismatch.
  • Poll getUpdates in a client instead of using webhooks — sidesteps the webhook path entirely rather than fixing it.

Note

A static-token scheme is weaker than HMAC (no body integrity, replayable), so it should be documented as such and is appropriate only where the transport is TLS and the source offers only a static token (Telegram's own model).

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 8 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/17/2026, 12:30:06 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
hammz assigned hammz9/16/2026, 10:17:51 PM
Editable. Press Enter to edit.

hammz commented 9/17/2026, 12:30:22 AM

Thanks @magistr for reporting this! Rather than adding a single static-token scheme, we've added webhook extensions: extensions can now ship their own webhook verifiers, and swamp serve resolves them by @collective/name scheme (in --webhook or serve.yaml), just like vaults and datastores. That should make it possible to support any webhook source, including Telegram's secret-token header, without changes to swamp itself. A conformance suite in @swamp-club/swamp-testing helps authors validate their handlers. This has been merged and shipped. We appreciate your contribution to swamp.

Sign in to post a ripple.