Skip to content

feat(webhooks): Add REST API endpoint for webhook URL management#115861

Open
Christinarlong wants to merge 6 commits into
masterfrom
christinarlong/legacy-webhook-api-endpoint
Open

feat(webhooks): Add REST API endpoint for webhook URL management#115861
Christinarlong wants to merge 6 commits into
masterfrom
christinarlong/legacy-webhook-api-endpoint

Conversation

@Christinarlong
Copy link
Copy Markdown
Contributor

@Christinarlong Christinarlong commented May 19, 2026

Summary

  • CRD endpoint that directly sets the ProjectOption for the list of urls to send on
  • PUT endpoint to enable/disable the webhook sending for a project

PR 4 of the legacy webhook plugin migration. Independent of PRs 1-3 — this endpoint can land at any time.

Refs: https://linear.app/getsentry/project/migrate-legacy-webhooks-away-from-plugins-c7549d6ed29b

Create a project-scoped endpoint at /legacy-webhooks/ that reads and
writes the same webhooks:urls ProjectOption used by the plugin:

- GET: returns {"urls": [...]}
- POST: accepts {"urls": [...]}, validates HTTP/HTTPS + is_valid_url
- DELETE: clears the webhooks:urls option

This replaces the plugin settings UI endpoint and can be used by the
frontend independently of the plugin system.

Refs: https://linear.app/getsentry/project/migrate-legacy-webhooks-away-from-plugins-c7549d6ed29b

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label May 19, 2026
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label May 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🚨 Warning: This pull request contains Frontend and Backend changes!

It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently.

Have questions? Please ask in the #discuss-dev-infra channel.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

📊 Type Coverage Diff

✅ No new type safety issues introduced. Coverage: 93.59%

Christinarlong and others added 2 commits May 28, 2026 14:29
Use LegacyWebhookUrlsSerializer with CharField + URLValidator(schemes=
["http", "https"]) for format/scheme validation, and is_valid_url for
IP blocklist checking. Update error assertions in tests.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
- Add LegacyWebhookUrlsSerializer with URLValidator for scheme check
  and is_valid_url for IP blocklist
- Add LegacyWebhookEnabledSerializer for PUT enabled/disabled toggle
- GET now returns both urls and enabled state
- PUT sets webhooks:enabled (same ProjectOption the plugin uses)
- Fix Serializer type arg (Serializer[None]) for mypy

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
@Christinarlong Christinarlong marked this pull request as ready for review May 28, 2026 22:12
@Christinarlong Christinarlong requested review from a team as code owners May 28, 2026 22:12
Comment thread src/sentry/api/endpoints/project_legacy_webhooks.py
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit aefe4c8. Configure here.

Comment thread src/sentry/api/endpoints/project_legacy_webhooks.py Outdated
…alizer

Django's URLValidator raises django.core.exceptions.ValidationError,
which is not caught by DRF's serializer validation pipeline, causing
a 500 instead of a 400 on invalid URLs.
@Christinarlong Christinarlong marked this pull request as ready for review May 29, 2026 01:21
Copy link
Copy Markdown
Member

@leeandher leeandher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design nit about PUT vs POST, wondering if it'll be easier for you to just consolidate into one method, since updates/creates both overwrite whatever data was there before anyway, and it's not obvious which attributes are available on each method

either way i'll defer to you! feel free to keep both if its meant to be compatible with a UI/considerations that I'm missing

Comment on lines +62 to +69
def post(self, request: Request, project: Project) -> Response:
serializer = LegacyWebhookUrlsSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=400)

urls = serializer.validated_data["urls"]
ProjectOption.objects.set_value(project, "webhooks:urls", "\n".join(urls))
return Response({"urls": urls}, status=200)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this mean to create both project option rows you'd have to make a POST and PUT request to set both? Wondering if the PUT can be removed and have the POST just handle both, especially if it overrides the existing values anyway, but I might be missing something

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 POST should accept enabled as well.

urls_raw = ProjectOption.objects.get_value(project, "webhooks:urls", default="")
urls = split_urls(urls_raw)
enabled = ProjectOption.objects.get_value(project, "webhooks:enabled", default=False)
return Response({"urls": urls, "enabled": enabled})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: optional, but you could use a type or serializer here that gets used on all the methods (except delete probably)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants