Skip to content

fix: correct verification facts in 7 skills - #208

Merged
garethx merged 7 commits into
mainfrom
fix/verified-verification-facts
Sep 30, 2026
Merged

garethx merged 7 commits into
mainfrom
fix/verified-verification-facts

Conversation

@garethx

@garethx garethx commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Fixes verification facts in seven provider skills that disagreed with the vendor's own docs or official SDK source. Each fix also updates that provider's notes in providers.yaml, so regenerating the skill won't bring the error back. Example code changed only for GitLab and Adyen. The other five fixes are prose only.

Twilio was also on the list, but the skill and its brief already say HMAC-SHA1 with X-Twilio-Signature, so nothing changed there. The Twilio error (HMAC-SHA256 listed as an algorithm, and I-Twilio-Idempotency-Token listed as a secret header) is in the webhook-registry row, which is a different repo.

GitLab (gitlab-webhooks)

Was wrong: "GitLab uses simple token comparison (not HMAC)". The skill covered only X-Gitlab-Token.

Now: Since GitLab 19.0 (GA in 19.1) a webhook can have a signing token, and GitLab recommends it. Delivery follows the Standard Webhooks spec:

  • signed content: {webhook-id}.{webhook-timestamp}.{raw body}
  • key: the signing token with whsec_ stripped, then base64-decoded
  • webhook-signature holds space-separated v1,<base64> HMAC-SHA256 values
  • compare in constant time

X-Gitlab-Token is kept as the legacy option. All three examples verify the signature over the raw body when webhook-signature is present, and fall back to X-Gitlab-Token otherwise, which is GitLab's migration advice. A request that carries a signature never falls back to the token.

GitLab says to check that webhook-timestamp is "recent" but gives no number, and the spec only asks for "some allowable tolerance". The examples use 5 minutes, which is the default in the Standard Webhooks reference libraries. The SKILL.md, references, example READMEs, .env.example files, root README row and marketplace description are updated to match.

Sources:

Tests: 8 new tests per suite. They include the published Standard Webhooks test vector (msg_p5jXN8AQM9LWM0D4loKWxJek, 1614265330, v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=), a match on one of several signatures, a tampered body, the wrong key, a stale timestamp, no fallback on a bad signature, and a missing webhook-id.

  • express: 26 passed (was 18)
  • nextjs: 26 passed (was 18), run on Node 24.11.1 / npm 11.6.2
  • fastapi: 27 passed (was 19), in a fresh venv from requirements.txt
  • validate-provider.sh gitlab-webhooks: PASSED

Adyen (adyen-webhooks)

Was wrong: "Each field value is escaped (\ → \\, : → \:)". The Python snippets and examples/fastapi escaped each value before joining.

Now: The eight values are joined with : exactly as they are, with no escaping. The escaping is removed from SKILL.md, references/verification.md and examples/fastapi/main.py. The difference only shows when a signed field such as merchantReference contains : or \. In that case the old FastAPI code would reject a genuine delivery. The Express and Next.js examples use the SDK, which already did this correctly.

Sources:

Tests: Added a regression test to each suite: a merchantReference of Order:42\A must be signed verbatim.

  • express: 12 passed
  • nextjs: 10 passed (Node 24)
  • fastapi: 14 passed, including Adyen's documented reference signature
  • validate-provider.sh adyen-webhooks: PASSED

Square (square-webhooks)

Was wrong: "Square also still sends a deprecated x-square-signature (HMAC-SHA1) header". The overview table said "Legacy ... still sent, deprecated".

Now: Square's docs document only x-square-hmacsha256-signature. x-square-signature is described as undocumented and seen on a live sandbox delivery (2026-08, square-version: 2026-07-15). The skill no longer calls it deprecated or legacy on Square's authority. It also doesn't claim Square has stopped sending it, because that can't be verified.

Source: https://developer.squareup.com/docs/webhooks/step3validate: "All webhook notifications from Square include an x-square-hmacsha256-signature header." None of the webhook pages in Square's llms.txt mention x-square-signature.

Tests: prose only. validate-provider.sh square-webhooks: PASSED

Intercom (intercom-webhooks)

Was wrong: "X-Body-Signature | (Some accounts) alternate name for the same value"

Now: X-Hub-Signature (sha1=<hex HMAC-SHA1>) is the only webhook signature header. A note explains that X-Body-Signature signs Canvas Kit requests with HMAC-SHA256, which is a different product.

Sources:

Tests: prose only. validate-provider.sh intercom-webhooks: PASSED

Linear (linear-webhooks)

Was wrong: "Linear has no first-party Node SDK helper for verifying webhooks" and "@linear/sdk ... does not ship a webhook verification helper"

Now: Documents LinearWebhookClient from @linear/sdk/webhooks (v97.0.0). Its verify(rawBody, signature) checks the hex HMAC-SHA256 against Linear-Signature, enforces ±60 s on the signed webhookTimestamp, and throws on failure. The manual examples are kept because they do the same checks.

Source: https://github.com/linear/linear/blob/master/packages/sdk/src/webhooks/client.ts: public verify(rawBody: Buffer, signature: string, timestamp?: number | string): boolean. I confirmed this in the published 97.0.0 tarball, whose exports include ./webhooks.

Tests: prose only. validate-provider.sh linear-webhooks: PASSED

HubSpot (hubspot-webhooks)

Was wrong: "HubSpot does not provide an SDK helper for webhook signature verification"

Now: Documents the helper in each official SDK:

  • Node @hubspot/api-client 14.0.1: Signature.isValid
  • PHP 14.1.0: \HubSpot\Utils\Signature::isValid
  • Python 12.0.0: Signature.is_valid
  • Ruby 20.0.0: Hubspot::Helpers::Signature#is_valid

All four support v1, v2 and v3, and raise on a v3 timestamp older than 5 minutes. Two caveats are noted: the helpers take the URL as given, and the Node helper compares with ===. The manual examples are kept.

Sources:

Tests: prose only. validate-provider.sh hubspot-webhooks: PASSED

Stripe (stripe-webhooks)

Was wrong: "The v1 signature is the current version. Ignore v0 (legacy)."

Now: v1 is the only valid live scheme. v0 is a fake signature that Stripe adds to test events to aid testing. Ignore it, but it is not a legacy scheme.

Source: https://docs.stripe.com/webhooks: "To aid with testing, Stripe sends an additional signature with a fake v0 scheme, for test events."

Tests: prose only. validate-provider.sh stripe-webhooks: PASSED

🤖 Generated with Claude Code

garethx and others added 7 commits September 30, 2026 16:19
Stripe's docs: "Currently, the only valid live signature scheme is v1. To aid
with testing, Stripe sends an additional signature with a fake v0 scheme, for
test events." The reference called v0 "legacy". Reworded, and added the same
fact to the providers.yaml brief so regeneration keeps it.

Docs: https://docs.stripe.com/webhooks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md and the verification reference said Linear has no first-party SDK
helper. @linear/sdk 97.0.0 exports LinearWebhookClient from
@linear/sdk/webhooks; verify(rawBody, signature) checks the hex HMAC-SHA256
of the raw body against Linear-Signature and enforces +/-60 s on the signed
webhookTimestamp body field, throwing on failure. Documented the helper and
kept the manual verification the examples use (it is equivalent). Brief
updated so regeneration doesn't reintroduce the claim.

Source: https://github.com/linear/linear/blob/master/packages/sdk/src/webhooks/client.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md and the verification reference said HubSpot has no SDK helper for
webhook verification. All four official client libraries have one:
Signature.isValid in Node (@hubspot/api-client 14.0.1) and PHP (14.1.0),
Signature.is_valid in Python (12.0.0) and Ruby (20.0.0). They support v1, v2
and v3 and raise on a v3 timestamp older than 5 minutes. Documented them, with
the caveats that they take the URL as given and the Node one compares with ===,
and kept the manual examples. Brief updated so regeneration keeps the fact.

Sources:
- https://github.com/HubSpot/hubspot-api-nodejs/blob/14.0.1/src/utils/signature.ts
- https://github.com/HubSpot/hubspot-api-python/blob/v12.0.0/hubspot/utils/signature.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overview listed X-Body-Signature as a "(some accounts) alternate name" for
the webhook signature. Intercom's webhook docs (all versions) name only
X-Hub-Signature (sha1=<hex HMAC-SHA1>). X-Body-Signature appears only on the
Canvas Kit page, where it signs Canvas Kit requests with HMAC-SHA256. Removed
it from the webhook header table, added a note explaining the difference, and
said the same in the providers.yaml brief.

Sources:
- https://developers.intercom.com/docs/references/webhooks/webhook-models
- https://developers.intercom.com/docs/canvas-kit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill called x-square-signature (HMAC-SHA1) a "deprecated"/"legacy" header
that Square "still sends". Square's current docs (step3validate and the other
webhook pages in llms.txt) document only x-square-hmacsha256-signature and say
nothing about x-square-signature, so neither "deprecated" nor "legacy" has a
Square source. The only evidence for it is our own 2026-08 sandbox capture.
Relabelled it as undocumented and observed on a live delivery, without claiming
Square has stopped sending it. Brief updated to match.

Docs: https://developer.squareup.com/docs/webhooks/step3validate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKILL.md, references/verification.md and the FastAPI example escaped '\' and
':' in each value before joining. Adyen's verify-hmac-signatures docs describe
no escaping ("use a colon (":") to delimit the values"), and every official
library joins the raw values for a NotificationRequestItem: Node
(hmacValidator.getDataToSign, 32.2.0), Java (Util.implode), Python
(generate_notification_sig), PHP and Ruby. The Node SDK's escaping branch only
runs for objects whose values are all strings, never for a
NotificationRequestItem. The mismatch only shows when a signed field such as
merchantReference contains ':' or '\', where the escaped version would reject
genuine deliveries.

- Removed escaping from the prose, both Python snippets and examples/fastapi.
- Added a regression test to all three example suites: a merchantReference
  containing ':' and '\' must be signed verbatim (Express/Next.js pin the SDK's
  behaviour, FastAPI pins the manual implementation).
- Brief updated so regeneration doesn't reintroduce escaping.

Docs: https://docs.adyen.com/development-resources/webhooks/secure-webhooks/verify-hmac-signatures/
SDK:  https://github.com/Adyen/adyen-node-api-library/blob/main/src/utils/hmacValidator.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill said GitLab "uses simple token comparison (not HMAC)" and only
covered X-Gitlab-Token. Since GitLab 19.0 (GA in 19.1) webhooks can have a
signing token, and GitLab's docs recommend it over the secret token, which
they call "not recommended for new webhooks". Delivery follows the Standard
Webhooks spec:

- signed content "{webhook-id}.{webhook-timestamp}.{raw body}"
- key: signing token with whsec_ stripped, base64-decoded
- webhook-signature: space-separated "v1,<base64 HMAC-SHA256>" entries
- constant-time compare; check webhook-timestamp is recent (GitLab gives no
  number; 5 minutes is the Standard Webhooks reference library default)

Changes:
- SKILL.md, references (verification, setup, overview): signing token as the
  recommended path, X-Gitlab-Token kept as the legacy option, GitLab's
  migration advice, API signing_token format.
- Express, Next.js and FastAPI examples now verify webhook-signature over the
  raw body when it is present (GITLAB_WEBHOOK_SIGNING_TOKEN) and fall back to
  X-Gitlab-Token otherwise. A request carrying a signature never falls back.
- Eight new tests per suite, including the published Standard Webhooks test
  vector (whsec_MfKQ9r8G..., msg_p5jXN8AQM9LWM0D4loKWxJek, 1614265330).
- providers.yaml brief, README row and marketplace description updated.

Docs: https://docs.gitlab.com/user/project/integrations/webhooks/#signing-tokens
Spec: https://www.standardwebhooks.com/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@garethx
garethx merged commit e4ffd3e into main Sep 30, 2026
25 checks passed
@garethx
garethx deleted the fix/verified-verification-facts branch September 30, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant