Repository navigation
feat: add sentry-webhooks skill - #212
Merged
Merged
Conversation
Verified against the Sentry docs payload samples before changing anything:
- seer.pr_created / seer.pr_ready_for_review: data.pull_requests entries are
{pull_request: {pr_number, pr_url, pr_id}, repo_name, provider}. The PR
fields are nested under pull_request; reading pr.pr_number off the array
element returned undefined in all three examples.
- preprod_artifact.build_distribution_completed: data.gitInfo has no `branch`
field. The documented branch fields are headRef / baseRef.
- references/overview.md listed the pull_requests shape flattened.
Also adds the integration files the review pass does not write: the README
row, the providers.yaml research brief, and the marketplace.json entry
(regenerated with sync-marketplace, not hand-merged).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xFvQczfSFGTYdJ9BgoHVM
hookdeck/core#5771 adds SENTRY as an HMAC alias — sha256, hex, header_key sentry-hook-signature, one secret labelled Client Secret, which is the scheme this skill already describes. Replaces the "no SENTRY source type in Hookdeck" note in SKILL.md and references/setup.md. Records why that source type covers Sentry-Hook-Signature only: the Sentry-App-Signature requests are synchronous request/response calls to the component's own path, and select-options and issue-link requests expect a JSON reply Sentry validates, so a Hookdeck source cannot usefully proxy them. The examples still accept both header names, because a handler's own endpoint can legitimately receive either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012xFvQczfSFGTYdJ9BgoHVM
garethx
marked this pull request as ready for review
October 2, 2026 14:47
# Conflicts: # providers.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
sentry-webhooksskill for Sentry's Integration Platform webhooks — what an internal integration (org-scoped, the common case) or public integration emits. Self-hosted Sentry runs the same code, so only the host differs.There is no
SENTRYsource type in Hookdeck today (checkedhookdeck.com/docs/sourceson 2026-10-01: every "sentry" hit on that page is Sentry's own JS debug-id shim, not a source entry), so there was no Hookdeck-side config to disambiguate a variant against. The scheme below was resolved from Sentry's own server code instead of from the docs alone — which turned out to matter, because the published snippets are wrong.The scheme
HMAC-SHA256, lowercase hex, over the raw request body, keyed with the integration's Client Secret as raw UTF-8.
SentryApp.build_signature(src/sentry/sentry_apps/models/sentry_app.py):The header value is a bare 64-char hex digest — no
v1=, not=, no list.Four things a reviewer will otherwise re-flag
Both
Sentry-Hook-SignatureandSentry-App-Signatureare accepted, deliberately. Sentry's UI-component external requests (select_options.requested,external_issue.created/linked,alert_rule_action.requested) use the samebuild_signaturebut send the second header name — verified insrc/sentry/sentry_apps/external_requests/*.py. Sentry's own reference app checks both on one endpoint, commented verbatim: "HACK: The signature header may be one of these two values". This is not a fabricated header.The examples deliberately disagree with Sentry's documented snippets. The docs verify
JSON.stringify(request.body)/json.dumps(request.body). Both re-serialize a parsed body, and both are latent bugs. Sentry serializes with simplejsonseparators=(",", ":")(compact, matchingJSON.stringify) but leavesensure_ascii=Truein place — see_default_encoderinsrc/sentry/utils/json.py— so Sentry emits\uXXXXescapes whereJSON.stringifyemits the literal character: any accent or emoji in an issue title, comment or username makes the documented JS snippet reject a valid delivery. The documented Python snippet is worse — stdlibjson.dumpsdefaults to", "/": "separators, so it diverges on any payload with more than one key. Every example verifies the raw body and says why.The timestamp is not signed, so there is no cryptographic replay protection.
Sentry-Hook-Timestampis sent but excluded from the signed string. The tolerance check is opt-in and documented as a dampener only; real protection is deduplication on theRequest-IDheader, since the body carries no delivery id.Empty bodies are legitimate. Some requests arrive with
b''andContent-Type: application/json, andselect_options.requestedsigns""outright.express.json()would coerce that to{}and fail verification — Sentry's own TS example special-cases it. Using the raw body avoids the problem, which is a second reason for the rule above.Routing
The resource is only in the
Sentry-Hook-Resourceheader — the body has notypeoreventfield, justaction. The event token isheader + "." + body.action. Noteevent_alertis the issue-alert resource: a handler keyed onissue_alert.triggerednever fires.Resources and actions come from
SentryAppEventType/sentry_apps/utils/webhooks.py, cross-checked against the docs. Four tokens are in the server enum but absent from the docs —metric_alert.open,seer.pr_ready_for_review,seer.iteration_started,seer.iteration_completed— and are handled but labelled undocumented rather than asserted.issue.ignoredis the wire token the docs callarchived; both are handled, neither dropped.Two sibling surfaces are described but not conflated with the above: service hooks (
X-ServiceHook-Signature, keyed with the hook's own secret, feature-flagged) and the legacy WebHooks plugin (completely unsigned, no longer documented — the skill says there is nothing to verify there rather than offering a verifier).Review findings
The accuracy pass raised 9 issues, 6 fixed in-run. Each was confirmed against a primary source before acting:
error.createdreadsdata.error.issue_id, notdata.event.issue_id. The errors doc page's attribute list claimsdata['event']['issue_id'], but its own payload sample has nodata.eventkey at all, so the docs contradict themselves and the reviewer was right.hmac.compare_digestonstrvalues; Starlette decodes headers as latin-1 andcompare_digestraisesTypeErroron non-ASCIIstr, turning a junk signature byte into a 500 instead of a 401. Both sides are now encoded.printf '%s' "$(cat body.json)", whose command substitution strips trailing newlines, so it would not hash the captured bytes.data.pull_requestsentries nest the PR underpull_request({pull_request: {pr_number, pr_url, pr_id}, repo_name, provider}), so the generatedpr.pr_numberreadundefinedin all three examples; anddata.gitInfo.branchdoes not exist (the documented fields areheadRef/baseRef).Delivery behaviour is documented as Sentry actually implements it: respond within 1 second, no retries, and repeated failures trip a per-integration circuit breaker that can disable the webhook (
_notify_webhook_disabledinsrc/sentry/utils/sentry_apps/webhooks.py). No source-IP allowlist is published, and the skill says so rather than inventing one.Testing
validate-provider.sh sentry-webhooks— passesNot live-verified — no Sentry test account was supplied for this run, so the digest has not been recomputed against a real delivery. Everything above is from Sentry's server source and its docs' payload samples.
🤖 Generated with Claude Code
https://claude.ai/code/session_012xFvQczfSFGTYdJ9BgoHVM