Skip to content

Commit 77225f9

Browse files
billyvgclaude
andcommitted
fix(integrations): Make webhook sentry_headers stable across calls
Cache the AppPlatformEvent.sentry_headers property so the Request-ID, timestamp, and signature are generated once per event. Previously the property recomputed them on each access, so the values logged to the webhook request buffer / debug UI differed from those actually sent, misleading developers debugging deliveries. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 773eee4 commit 77225f9

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

src/sentry/sentry_apps/api/serializers/app_platform_event.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections.abc import Mapping
22
from enum import StrEnum
3+
from functools import cached_property
34
from time import time
45
from typing import Any, TypedDict
56
from uuid import uuid4
@@ -89,12 +90,15 @@ def body(self) -> str:
8990
)
9091
)
9192

92-
@property
93+
@cached_property
9394
def sentry_headers(self) -> dict[str, str]:
9495
"""Headers Sentry sets on every webhook request.
9596
9697
These are the only headers recorded in the request buffer / debug UI:
9798
custom webhook headers may carry secrets and must never be logged there.
99+
100+
Cached so the Request-ID, timestamp, and signature are computed once and
101+
stay consistent between the sent request and the logged buffer entry.
98102
"""
99103
request_uuid = uuid4().hex
100104

tests/sentry/sentry_apps/api/serializers/test_app_platform_event.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,21 @@ def test_sentry_headers_exclude_custom_headers(self) -> None:
134134

135135
assert "Authorization" not in result.sentry_headers
136136
assert result.headers["Authorization"] == "Bearer secret"
137+
138+
def test_sentry_headers_are_stable_across_calls(self) -> None:
139+
# The Request-ID/timestamp logged to the buffer must match what was sent,
140+
# so sentry_headers is computed once per event rather than per access.
141+
result = AppPlatformEvent[dict[str, Any]](
142+
resource=SentryAppResourceType.ISSUE,
143+
action=IssueActionType.ASSIGNED,
144+
install=self.install,
145+
data={},
146+
)
147+
148+
first = result.sentry_headers
149+
second = result.sentry_headers
150+
assert first["Request-ID"] == second["Request-ID"]
151+
assert first["Sentry-Hook-Timestamp"] == second["Sentry-Hook-Timestamp"]
152+
# The headers actually sent carry the same values that get logged.
153+
assert result.headers["Request-ID"] == first["Request-ID"]
154+
assert result.headers["Sentry-Hook-Timestamp"] == first["Sentry-Hook-Timestamp"]

0 commit comments

Comments
 (0)