Fail-Open by Default: Webhook Authenticity for Multi-Channel Agent Bridges
Executive Summary
A persistent agent that talks to people over Telegram, Slack, Lark/Feishu, or WeChat almost always receives those messages through an inbound webhook: the provider POSTs to a public URL whenever a user sends something, and the bridge treats the body as a new inbound message or command. That makes the webhook endpoint the actual front door of the agent, and the interesting security question is not "was this HTTP request well-formed" but "did this request really come from the provider, and only once." Every major provider gives receivers a way to answer the first half, at different strengths — a static shared token (in a header for Telegram, inside the JSON body for Feishu's Verification Token mode), a signature over a few request parameters that leaves the body unsigned (WeChat's default plain mode), or a computed signature over the raw body (usually together with a timestamp), and only the last binds the content to the provider — but none of those formulas answers "only once" by itself: a byte-identical replay reproduces the same valid signature, so freshness windows and de-duplication are policies the receiver has to enforce, and channels whose authenticator carries no timestamp at all get no freshness signal from it. There, a de-duplication key only defends against replay if the authenticator covers it: GitHub's X-GitHub-Delivery header sits outside its body-only HMAC, so a store keyed on it absorbs provider redeliveries but lets an attacker resend a captured body under a fresh header, and Telegram's static header binds nothing to the request at all. Two 2026 reports about AI-agent bridges show where the code around these schemes goes wrong. CVE-2026-44109 (CVSS 3.1 9.8, OpenClaw's Feishu plugin) is a confirmed fail-open default: with no signing key configured, the signature check returned "valid". A companion issue filed against a fork, remoteclaw, reports the same missing-key fail-open plus a second, weaker concern: the fork has no signature gate of its own ahead of the SDK's JSON parsing — a parser exposure, not a demonstrated bypass. The fix is not a stronger algorithm; Feishu's own scheme is fine. It is defaults and ordering: capture raw bytes before any parsing, refuse to start without a secret (rather than trusting a library default), reject on any verification failure, enforce a freshness window where the timestamp is signed, parse or decrypt only requests that have already passed verification, and de-duplicate on an identity the signature actually covers (an ID inside the signed body, or a digest of the signed bytes). Strict verify-before-parse is only available where the authenticator sits in headers or the query string; a token carried inside the body, such as Feishu's Verification Token, needs a deliberately minimal, bounded pre-parse just to read it. And a passed check proves only what the chosen scheme covers — the exact bytes for a raw-body signature, mere possession of a secret value for a static token or WeChat's plain-mode signature — and never that the message content is trustworthy.
The front door of an agent, not a payment processor
Webhook security is usually discussed in payments and CI/CD contexts — did this "payment succeeded" callback really come from Stripe, did this "build finished" callback really come from GitHub. A persistent multi-channel agent has the identical exposure with a different payload: the webhook is not confirming a fact, it is delivering a command. When Zylos-style comm bridges receive a Telegram or Lark webhook, the body becomes a chat turn that the agent runtime reads, and depending on what tools are enabled, potentially acts on. An unauthenticated party who can reach the webhook port does not need to steal a session token; they only need to make one HTTP POST that looks like a real user message.
This reframes the design question. In a payment webhook, forging a request produces a wrong ledger entry. In an agent message bridge, forging a request can produce an instruction the agent executes — the same class of impact as prompt injection, but delivered at the transport layer instead of inside document content, and preventable with mechanisms that have existed for years and are simply misapplied.
What providers actually give you
The mechanisms cluster into two families.
Shared bearer-style header. Telegram's webhook mode lets the bot owner set an arbitrary secret_token (1–256 characters) when calling setWebhook; every subsequent update POST carries it back in X-Telegram-Bot-Api-Secret-Token. The receiver's whole job is to compare that header against the configured value and reject on mismatch. Telegram Bot API documents the field; independent write-ups converge on constant-time comparison and an HTTP 403 on failure. Because the header is the same static value on every request, it carries no timestamp and no per-request signature: it can tell a receiver the request knows the secret, but not whether the request is fresh or has been seen before. The only de-duplication handle Telegram provides is update_id in the JSON body, which the same page says "allows you to ignore repeated updates" when using webhooks — but the header does not bind the body, so anyone who has captured one request holds the secret and can send any body with any update_id. On Telegram, update_id de-duplication is idempotency against repeated deliveries, not a defence against an attacker; that rests on the token staying secret. Feishu/Lark offers a comparable lightweight option as an alternative to its signed mode, a static "Verification Token" — but it travels inside the JSON event body rather than in a header: token at the top level of v1 events, header.token in v2 events. Feishu's guide calls this check simple but less secure, notes that the token is sent in plaintext when no Encrypt Key is configured, and says that once an Encrypt Key is set the token can only be read after decrypting the event, at which point it recommends the signature check instead. A receiver therefore has to parse at least part of the body before it can compare the token (the gate below treats this case separately), and, as with Telegram, a matching token shows the sender knows the secret, not that the rest of the body is what Feishu sent. Feishu Open Platform: event subscription overview, Feishu Open Platform: encrypt-key encryption configuration
Computed signature over the raw body, usually plus a timestamp. Slack, GitHub, Stripe, and Feishu/Lark's stronger mode all follow the same shape with different string layouts — and in every case the formula only proves who produced the bytes. It does not compare the timestamp against a clock or remember what it has already accepted; a byte-identical replay recomputes to the same valid signature. Freshness and de-duplication are separate checks that either the receiver or the provider's SDK has to perform:
- Slack:
v0=HMAC-SHA256(signing_secret, "v0:" + timestamp + ":" + raw_body), sent asX-Slack-Signature. The signedX-Slack-Request-Timestampmakes a freshness check possible, and Slack's guide tells the receiver to perform it: if the timestamp is more than five minutes from local time, "It could be a replay attack, so let's ignore it." There is no nonce, so a replay inside that window still verifies unless the receiver de-duplicates. Slack: Verifying requests from Slack - GitHub:
sha256=HMAC-SHA256(webhook_secret, raw_body)inX-Hub-Signature-256, compared with a constant-time function; the legacy SHA-1 header is "only included for legacy purposes." The HMAC covers the body only — no timestamp is signed, so there is nothing to run a freshness window against. GitHub's replay guidance is instead to "use theX-GitHub-Deliveryheader to ensure that each delivery is unique per event" (a requested redelivery reuses the original value). That header, a GUID, is not part of the signed body, so it identifies honest deliveries but authenticates nothing: a captured body andX-Hub-Signature-256resent with a newly invented delivery GUID still verify, and a store keyed only on the header dispatches them again. Replay defence on GitHub has to key on the signed bytes (see step 6 of the gate below). GitHub: Validating webhook deliveries, GitHub: Best practices for using webhooks - Stripe: the SDK's
constructEventrecomputes the signature from the raw body and a header timestamp, with a default 300-second tolerance; a tolerance of 0 disables the recency check entirely and is explicitly warned against. Here the official library performs the freshness check by default; duplicates are still the receiver's job — Stripe's guidance is to log processed event IDs and skip ones already seen. Stripe: Receive events in your webhook endpoint - Feishu/Lark encrypted mode:
X-Lark-Signaturemust equalSHA256(timestamp + nonce + encrypt_key + raw_body), using theX-Lark-Request-TimestampandX-Lark-Request-Nonceheaders, and the docs note the check can be done without decrypting or parsing the event. When Event Encryption is enabled the body itself is{"encrypt": "<base64>"}, decrypted with AES-256-CBC where the key isSHA256(encrypt_key)and the IV is the ciphertext's first 16 bytes. Feishu Open Platform: encrypt-key encryption configuration The timestamp and nonce are signed, but the signature-check documentation specifies no freshness window and no nonce store, and the official Node SDK's check (checkIsEventValidated) is a pure hash comparison with neither: re-running that predicate on a correctly signed request with a 2001 timestamp, then on the identical request a second time, accepts both. What Feishu does document is at-least-once delivery — failed pushes are retried at 15 seconds, 5 minutes, 1 hour and 6 hours, and duplicates can arrive even after a successful push — with the advice to make handling idempotent onevent_id(oruuidfor v1 events). Feishu Open Platform: event subscription overview
WeChat's official-account push mechanism is the outlier and, in its default configuration, the weakest of the group. In plain mode (the default), token, timestamp, and nonce are sorted lexicographically, concatenated, and hashed with SHA-1; the result must equal the signature query parameter. The body is not an input, so the check authenticates "a party that knows the shared token" for that timestamp and nonce, and a captured query string verifies just as well when attached to a different body. That is adequate only if the token itself stayed private and is paired with transport-level restrictions; it is not a signed-payload scheme and should not be treated as equivalent to GitHub or Stripe's guarantee. WeChat's safe mode (which WeChat recommends; a compatibility mode sends plaintext and ciphertext side by side) does bind content: the body carries an Encrypt field, AES-256-CBC-encrypted with a key derived from the configured EncodingAESKey, and a separate msg_signature query parameter is the SHA-1 of the sorted token, timestamp, nonce and that Encrypt value — the docs say to verify msg_signature, not signature, in this mode. Because the signed input is a field of the XML or JSON body rather than the raw bytes, even safe mode has to extract Encrypt before it can verify, and plaintext fields beside it in the envelope are not covered. WeChat: message push integration, WeChat: message encryption and decryption WeChat Pay's callback signing (RSA/HMAC over a certificate-backed scheme) is a completely different, stronger mechanism used only for payment notifications, not chat message pushes — conflating the two is a real and easy mistake when one component handles both.
Two 2026 bridge reports: the defaults were wrong, not the algorithm
The Feishu schemes above are sound. What failed — confirmed in one case, reported in the other — was the code around them.
CVE-2026-44109 (OpenClaw, versions before 2026.4.15, CVSS 3.1 base score 9.8, CWE-1188 "Initialization of a Resource with an Insecure Default") sat in the Feishu webhook handler's isFeishuWebhookSignatureValid() function: when the configured encryptKey was missing or blank, the function returned true — "missing key = auth passes for all requests" in the disclosure's own words — instead of refusing to serve. A second, independent bug in the card-action replay guard, beginFeishuCardActionToken(), returned true for a blank token without ever recording it in the de-duplication map, so a blank-token request could be replayed indefinitely. Combined, an attacker with network reach to the webhook port could POST an unsigned, crafted Feishu event straight into the agent's command-dispatch path; with execution tools enabled, that is unauthenticated remote code execution with the process's own privileges. The webhook listener itself also did not refuse to start without a key (a config-schema requirement added a month earlier was, per the write-up, only a partial mitigation), so an incomplete deployment could be exposed rather than failing to boot. The vendor shipped a fix on 2026-04-14 (commit c8003f1b) that inverted both booleans and added a startup guard that throws when webhook mode has no encryptKey; the CVE was published 2026-05-06. Note what this bug was not: in the last vulnerable commit, the handler already ran that check on the raw body before any JSON parsing (call site) — the ordering was right, the default was wrong. Ostorlab: Exploit CVE-2026-44109, CVE record: CVE-2026-44109
A companion issue filed against remoteclaw, a fork of OpenClaw, makes two claims of different strength. The first is a fail-open: encryptKey is optional in webhook connection mode, so a webhook account can be served with no signing key, whereas upstream OpenClaw's own isFeishuWebhookSignatureValid() "fails closed on exactly that state — with no encryptKey, every request is rejected." That fail-closed gate is OpenClaw's code, not the Lark SDK's. The fork hands requests straight to its vendored copy of the Lark Node SDK, and the official SDK's default is the opposite: its checkIsEventValidated() begins with if (!this.encryptKey) { return true; } (pinned source), so a missing key in a deployment that relies on the SDK alone means unsigned events are accepted. The second claim is about ordering: there is no in-repo signature check, so "the fork's own auth boundary sits after untrusted JSON has been handed to a vendored parser rather than before it." The report is explicit about the limit of that claim — it is "deliberately not a claim that no verification happens when encryptKey is set — the SDK may verify internally, and that has not been audited here." Reading the current official SDK source (the fork's vendored copy was not compared) fills in part of that gap without changing the conclusion: with a key configured, the SDK's default adapter JSON.parses the body, can answer the URL-verification challenge (decrypting it if needed), and only then recomputes the signature — over a re-serialized JSON.stringify of the parsed object rather than the raw bytes — before any registered event handler runs (adapter, dispatcher). That is unauthenticated input reaching a parser and a decryptor, but neither the report nor anything cited here shows a forged event getting past that check. The proposed fix mirrors standard webhook hygiene: make the key mandatory in webhook mode, port the upstream signature function and call it before any parsing, and add regression tests for the unset-key and bad-signature cases explicitly. The issue was open, with no maintainer response, at the time of writing. remoteclaw issue #3229
The two reports share one confirmed anti-pattern, independent of which provider's algorithm is in use: fail-open on absent configuration. It needs no cryptographic weakness to exploit — it is a pure control-flow defect that a unit test asserting "empty secret must reject" would have caught, and it can come from your own code (OpenClaw before 2026.4.15) or from a library default you did not override (the Lark SDK's return true). The second pattern, verify-after-parse, is a different kind of finding: it widens the attack surface to whatever the parser, decryptor or pre-verification side effects do with attacker-controlled bytes, but on the evidence available it is an exposure, not a demonstrated authentication bypass. Moving the check ahead of parsing is still worth doing — it is cheap, and it is the same raw-bytes discipline the signature needs anyway — but it should be argued as exposure reduction, not as the fix for a bypass nobody has shown.
The adjacent, older failure mode: signing a body you no longer have
A third failure pattern predates agent bridges and still resurfaces in them: computing or verifying a signature over a body that a web framework has already re-parsed and re-serialized. Express's express.json() (and equivalents in other stacks) parses the request body into an object; if a webhook handler then re-stringifies that object to check a signature, whitespace and key ordering can differ from the exact bytes the provider hashed, and legitimate, correctly-signed requests fail verification. The documented remediation is to mount a raw-body reader on the webhook route specifically, ahead of any general-purpose JSON body parser, so the exact bytes are available to the signature check — the same "capture raw bytes first" discipline that a correct pre-parse verification gate needs anyway. DEV Community: Webhook Signature Verification Fails — Your Framework Already Parsed the Body, DEV Community: Webhook Security Best Practices for Production 2025–2026
The operational danger of this class of bug is not the failure itself but the fix teams reach for under deadline pressure: disabling verification "temporarily" because it is blocking legitimate traffic. Two documented CVEs show what a silently bypassable verification path looks like once it ships. In the New API LLM gateway, the Stripe webhook secret defaulted to an empty string, the handler did not reject that state, and the Stripe Go SDK computed the HMAC with the empty key — so anyone could produce a valid signature, forge checkout.session.completed events and credit arbitrary quota (CVE-2026-41432, CVSS 3.1 7.1; GHSA-xff3-5c9p-2mr4). In the Easy Digital Downloads WordPress plugin, PayPal IPN verification was unconditionally skipped whenever the POST body contained verification_override=1, letting an unauthenticated actor submit a forged IPN that was treated as verified, though exploitation still needed a valid PayPal transaction ID for an order the attacker had placed (CVE-2025-11271, CVSS 3.1 5.3; CVE record). Neither is a chat bridge, but both are the same lesson restated: a verification path that can be silently bypassed by a missing value or a magic parameter is worse than no documented verification, because it looks secure in code review.
A verify-before-parse gate for a multi-channel bridge
Putting the provider mechanisms and the failure modes together, a bridge that fronts several chat providers for one agent needs one ingress-time gate per channel adapter, run in this order: verify the request under the channel's scheme, then parse or decrypt only requests that passed, then de-duplicate on an authenticated identity, and only then dispatch. How much "verify before parse" a channel can actually get depends on where its authenticator lives and what it covers, and the schemes surveyed above fall into three groups:
- Raw-body signatures carried in headers — Slack, GitHub, Stripe, Feishu's signature mode (the path for every encrypted Feishu event, see below). Verification needs only headers and the raw bytes, so nothing is parsed before authentication, and the bytes that pass are bound to the signing secret. The full recipe below applies.
- Header or query secrets that do not cover the body — Telegram's
X-Telegram-Bot-Api-Secret-Token, WeChat's plain-modesignature. These can also be checked before any parsing, which still keeps the parser away from anyone without the secret, but the body that passes is not authenticated: whoever holds the secret, or has captured one valid request, can attach any body. - Authenticators inside the body — Feishu's Verification Token on unencrypted events (
tokenin v1,header.tokenin v2), and WeChat's safe mode, whosemsg_signaturecovers the body'sEncryptfield rather than the raw bytes. Reading them requires a parse before authentication, so the parser exclusion promised in step 5 cannot hold. Keep that pre-parse deliberately small: enforce a hard size limit before reading the body, use a strict parser that rejects malformed input, extract only the authenticator field, compare it (or the recomputedmsg_signature) in constant time, and only then run the full parse, decryption and dispatch. Feishu's token then gives the same bearer-style guarantee as Telegram's header, withevent_idoruuidusable for idempotency against redelivery but not for replay defence; WeChat's safe mode binds the encrypted message itself. The Feishu case is limited to apps with no Encrypt Key: once one is set, the outer body is only{"encrypt": …}and the token exists only inside the ciphertext, so there is nothing to pre-parse. Encrypted Feishu events take the raw-body signature path instead: verifyX-Lark-Signaturefrom headers and raw bytes, then decrypt, and treat the inner token as an optional extra check after that. A token-only gate for encrypted events would have to decrypt before authenticating, which exposes the decryptor to unauthenticated input; it is not part of this recipe.
-
Capture exact bytes. Read the raw request body before any JSON/XML parser touches it; where the scheme signs the body, the signature must be computed over what was actually sent, not a re-encoded copy. For authenticators inside the body, the bounded pre-parse above runs on these bytes and is the only parse allowed before step 3.
-
Look up the per-channel secret and refuse to serve without one. No verification function should have a code path that returns "valid" when its key material is absent, empty, or unconfigured — that path should not exist, and the process should fail to start (or the route should return 500/503) rather than silently accept unsigned traffic. Enforce this in the bridge's own startup code rather than relying on a library: the official Lark Node SDK's event check returns
truewhen noencryptKeyis set, and the Stripe Go SDK in CVE-2026-41432 happily computed an HMAC with an empty secret. This directly targets the CVE-2026-44109 pattern. -
Recompute the provider-specific signature or compare the shared token, in constant time, matching each provider's documented scheme exactly (Slack's
v0:timestamp:bodystring is not interchangeable with GitHub's raw-body-only HMAC, and Feishu'stimestamp+nonce+key+bodyconcatenation order matters). Telegram's secret is a constant-time comparison of one header; WeChat's plain-modesignatureis computed from query parameters and covers no body bytes; Feishu's Verification Token (unencrypted events only) and WeChat safe mode'sEncryptfield come from the bounded pre-parse. -
Enforce a timestamp freshness window where the provider signs a timestamp — this is a receiver policy, not something the signature formula does for you. Slack's guide uses five minutes and Stripe's libraries default to five minutes; never set the tolerance to zero or disable it "for debugging" in a path that can reach production. Feishu signs a timestamp but documents no window, so pick one deliberately — and since Feishu retries failed pushes for hours, confirm how the timestamp header behaves on a retry before making the window strict. Telegram's static header, Feishu's Verification Token and GitHub's body-only HMAC carry no signed timestamp, so there is nothing to check here: on GitHub the replay horizon is set entirely by the step-6 store, and on Telegram and in Feishu's token mode there is no cryptographic replay control at all. For raw-body signatures and header or query secrets, steps 1–4 use only headers, query parameters and raw bytes, and nothing has been parsed or decrypted yet; for authenticators inside the body, the bounded pre-parse is the one exception.
-
Parse or decrypt only verified requests, with bounds. Only now hand the full body to a JSON/XML parser, with size and depth limits, and for Feishu's encrypted mode decrypt the
{"encrypt": …}envelope and parse the inner event. This step has to come before de-duplication because the IDs live inside the payload: Telegram'supdate_id, Stripe's eventid, Slack's payload IDs, and Feishu'sevent_id, which in encrypted mode exists only after decryption (Feishu's guide notes that the signature check needs no decryption but reading the event content does). For raw-body signatures and header or query secrets, verifying first means requests from anyone without the secret never reach a parser, a decryptor, or any pre-verification side effect such as an auto-answered challenge; for authenticators inside the body, the bounded pre-parse is the exposure that remains, which is why it is kept minimal. Only raw-body signatures (and, for the encrypted message, WeChat's safe mode) make what reaches the parser authenticated bytes; behind a header secret, Feishu's token or WeChat's plain-mode signature, the body is only one that arrived with a valid secret. That is exposure reduction plus the raw-bytes discipline from step 1: a correct check that runs after parsing but before dispatch still keeps forged events away from the agent, but it leaves the parser reachable by anyone who can reach the port. The remoteclaw report flags exactly this gap and is careful not to claim more. -
Validate the ID, de-duplicate on an authenticated identity, then dispatch. Reject a payload whose ID is missing or malformed, check the key against a bounded, expiring store, and only then hand the event to the dispatcher. Two different jobs share this store, and the key decides which one it does. Provider redelivery idempotency (providers deliver at least once) can use any ID the provider repeats on a retry. Attacker replay defence needs a key the attacker cannot change without breaking verification, which means something covered by the signature:
- Feishu
event_id(signature mode), Stripe eventid: inside the signed body, so the same key serves both jobs. For replay, retention only needs to outlast the freshness window, because an older timestamp already fails step 4. For idempotency, size retention to the retry schedule: Feishu's retry schedule above runs out to the 6-hour attempt, and Stripe retries for up to three days in live mode, with a new signature and timestamp on each attempt, so only the event ID identifies a Stripe retry. Slack also signs its timestamp; use an ID from the payload where one exists. - GitHub: no ID inside the HMAC. Keep
X-GitHub-Delivery(which a requested redelivery reuses) for idempotency only. For replay, keep a second store keyed on a digest of the exact signed bytes, e.g. SHA-256 of the raw body: a captured body resent under a fresh delivery GUID then hits the digest store, and a changed body fails step 3. With no signed timestamp, the digest store's retention is the whole replay horizon. Once an entry expires, the captured request verifies and dispatches again, so choose and document a retention period that reflects how long a replay would still do harm. The cost is that two genuinely distinct events with byte-identical bodies inside that period collapse into one. This should be rare for payloads that carry changing object state, but GitHub documents no guarantee against it, so handlers for which a dropped duplicate matters should know about this trade-off. - Telegram, Feishu's Verification Token mode, WeChat's plain mode: de-duplication on
update_id, onevent_idoruuid, or on any ID in a plain-mode WeChat body is idempotency only. None of these authenticators binds the body, so anyone holding a captured request can send fresh bodies with new IDs, and there is no authenticated identity to key on.
Freshness alone stops a stale replay but not an immediate one, and an expiring store stops a replay only while its entry lives. Neither check is provided by the signature formula itself.
- Feishu
A request that passes this gate has been verified under its channel's scheme, and that proves exactly what the scheme covers. A raw-body signature (Slack, GitHub, Stripe, Feishu's signature mode) shows that someone holding the signing secret — normally the provider — produced these exact bytes for this bot/app; WeChat's safe-mode msg_signature shows the same for the encrypted message. A static token (Telegram's header, Feishu's Verification Token) or WeChat's plain-mode signature shows only that the request presented a valid secret or signature value, which anyone who captured an earlier request can also do; it does not show that the body is what the provider sent. None of them shows that this is the first delivery — that is what steps 4 and 6 are for — and none says anything about whether the content of the message — the text the end user typed — should be trusted as an instruction. Provider signing authenticates the channel, not the sender's intent or the safety of what they wrote; those are the same content-level trust questions that apply to any user-supplied text reaching an agent, and they need to be handled separately, downstream of this gate, not folded into it.
What remains provider-specific and easy to get wrong
Two details don't generalize across the providers surveyed here and are worth flagging rather than papering over. First, WeChat's official-account scheme in its default plain mode is materially weaker than the others (SHA-1, a signature over the sorted triple that leaves the body unsigned, no documented replay-window mechanism) — a bridge that also speaks WeChat should not assume that leg carries the same guarantee as the Feishu or Slack leg. Either move the account to safe mode, which binds the encrypted message through msg_signature but still needs the bounded pre-parse described in the gate, or treat plain mode as its own explicitly limited path with compensating controls (network-level allowlisting of WeChat's published IP ranges, tighter rate limiting) that the other channels don't strictly require. Second, secret rotation needs a dual-secret window by design: generate the new secret, accept signatures from both the old and new secret simultaneously, cut the provider over, confirm new-secret traffic, then revoke the old one — a single-secret verification path forces an outage or a gap during rotation, which in turn creates pressure to skip rotation altogether.
Sources and scope
- Telegram Bot API — setWebhook / secret_token, Update.update_id — official spec for the static shared-token webhook header and the update ID used to ignore repeated updates.
- Slack: Verifying requests from Slack — v0 HMAC scheme, the receiver-side five-minute timestamp check, constant-time comparison.
- GitHub: Validating webhook deliveries and Best practices for using webhooks — X-Hub-Signature-256 over the body only, constant-time comparison,
X-GitHub-Delivery(outside the HMAC, so useful for redelivery idempotency rather than as a replay key). - Stripe: Receive Stripe events in your webhook endpoint — constructEvent, default five-minute library tolerance, warning against a tolerance of 0, a new signature and timestamp on every retry, event-ID de-duplication.
- Feishu Open Platform: encrypt-key encryption configuration — signature formula (checkable without decryption), AES-256-CBC event decryption required before the event content can be read, no freshness window or nonce store, and the Verification Token alternative (read from the request body; plaintext without an Encrypt Key, readable only after decryption with one).
- Feishu Open Platform: event subscription overview — v1 (
token) and v2 (header.token) Verification Token locations in the event body, at-least-once delivery, retry schedule,event_ididempotency. - larksuite/node-sdk at 394c830 —
checkIsEventValidated(returnstruewithout anencryptKey; no time comparison or replay store) and the default adapter's parse-then-verify order. The old-timestamp and repeated-request acceptance described above was reproduced by running that predicate locally; it covers the SDK check only, not provider delivery behavior. - Ostorlab: Exploit CVE-2026-44109 — OpenClaw Feishu Webhook Authentication Bypass to RCE — root-cause analysis, exploitation path, fix commit and dates; CVE record for CVSS 3.1 9.8, CWE-1188 and the 2026-05-06 publication date. OpenClaw's
monitor.transport.tsshows the fail-closed predicate and its pre-parse call site. - remoteclaw issue #3229 — optional encryptKey in webhook mode (a fail-open relative to upstream, consistent with the SDK's missing-key
return true) and the absence of an in-repo pre-parse gate (an exposure; keyed SDK verification explicitly not audited). - DEV Community: Webhook Signature Verification Fails — Your Framework Already Parsed the Body and Webhook Security Best Practices for Production 2025–2026 — raw-body-vs-reparsed-body verification trap and the Express-specific fix.
- GHSA-xff3-5c9p-2mr4 (CVE-2026-41432) and CVE-2025-11271 — the empty-secret Stripe handler and the
verification_overridePayPal IPN bypass, cited as unrelated real-world instances of the same "verification silently bypassable" family. - WeChat: message push integration and message encryption and decryption — plain-mode
signature(SHA-1 over sorted token, timestamp and nonce; no body input), the plain/compatibility/safe modes, and safe mode'smsg_signatureover the body'sEncryptfield plus AES-256-CBC decryption. WeChat Pay's separate certificate-based callback signing is drawn from vendor/developer documentation surfaced in aggregated search results and treated here as a comparison point rather than independently reproduced.
This article compares publicly documented provider mechanisms and publicly disclosed defects; it does not audit any specific Zylos component, and no claim is made that any Zylos channel bridge shares these defects. The verify-before-parse checklist is the author's synthesis of the cited sources, not a certified standard.

