Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1515ed7
fix most issues
jp-agenta Mar 12, 2026
5c68ba5
clean up notifications
jp-agenta Mar 12, 2026
a510c79
Merge branch 'release/v0.94.3' into fix/shady-webhooks
jp-agenta Mar 12, 2026
44dbb72
format / lint
jp-agenta Mar 12, 2026
64d5ba9
linf fix
jp-agenta Mar 12, 2026
fb4461a
Update web/oss/src/services/automations/api.ts
jp-agenta Mar 12, 2026
c51699e
Update web/oss/src/services/automations/api.ts
jp-agenta Mar 12, 2026
c27127d
Fix flow and bugs
jp-agenta Mar 13, 2026
aa6049b
fix copy
jp-agenta Mar 13, 2026
888fcd1
move Automation location in side menu
jp-agenta Mar 13, 2026
5dd787d
fix(frontend): lint fix in SecretRevealModal
mmabrouk Mar 13, 2026
3c7d436
fix(automations): keep saved webhooks active after save
mmabrouk Mar 13, 2026
c3e0f67
feat(automations): test draft webhooks before save
mmabrouk Mar 13, 2026
6612466
feat(automations): add delivery logs to the drawer
mmabrouk Mar 13, 2026
19023e2
fix(frontend): redesign delivery logs tab — simplify detail to raw JS…
mmabrouk Mar 13, 2026
178d6c0
fix(api,frontend): redact sensitive headers from webhook delivery rec…
mmabrouk Mar 13, 2026
3c75920
fix(api): redact sensitive headers from webhook delivery records
mmabrouk Mar 13, 2026
a7f850b
fix(frontend): keep automation logs scrolling contained
mmabrouk Mar 13, 2026
e953daa
Merge pull request #3988 from Agenta-AI/fix/redact-webhook-delivery-s…
mmabrouk Mar 13, 2026
63f91df
Merge branch 'fix/shady-webhooks' into fix/automations-draft-testing
mmabrouk Mar 13, 2026
5aa7945
fix(api): remove broken merge code from webhook task
mmabrouk Mar 13, 2026
2462142
fix(api): restore webhook header redaction in shared delivery helper
mmabrouk Mar 13, 2026
2c2c519
Merge remote-tracking branch 'origin/fix/automations-draft-testing' i…
mmabrouk Mar 13, 2026
618f57d
fix(api): persist edited webhook secrets when missing secret ids
mmabrouk Mar 13, 2026
d9c0582
Merge pull request #3987 from Agenta-AI/fix/automations-delivery-logs
mmabrouk Mar 16, 2026
6ae46fe
Merge pull request #3986 from Agenta-AI/fix/automations-draft-testing
mmabrouk Mar 16, 2026
1d1d879
fix(webhooks): simplify subscription testing and drop unused flags
mmabrouk Mar 16, 2026
ee11370
chore(webhooks): apply lint fixes
mmabrouk Mar 16, 2026
1faca53
fix(webhooks): remove old test endpoints
mmabrouk Mar 16, 2026
0b3241d
chore(docs): add annotation queues and evaluator playground updates t…
mmabrouk Mar 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions api/entrypoints/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@

from oss.src.utils.env import env
from entrypoints.worker_evaluations import evaluations_worker
from entrypoints.worker_webhooks import webhooks_worker
import oss.src.core.evaluations.tasks.live # noqa: F401
import oss.src.core.evaluations.tasks.legacy # noqa: F401
import oss.src.core.evaluations.tasks.batch # noqa: F401
Expand Down Expand Up @@ -277,7 +276,6 @@ async def lifespan(*args, **kwargs):
webhooks_service = WebhooksService(
webhooks_dao=webhooks_dao,
vault_service=vault_service,
webhooks_worker=webhooks_worker,
)


Expand Down
10 changes: 9 additions & 1 deletion api/oss/src/apis/fastapi/webhooks/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List, Optional
from typing import List, Optional, Union
from uuid import UUID

from pydantic import BaseModel

Expand All @@ -24,6 +25,13 @@ class WebhookSubscriptionEditRequest(BaseModel):
subscription: WebhookSubscriptionEdit


class WebhookSubscriptionTestRequest(BaseModel):
subscription_id: Optional[UUID] = None
subscription: Optional[
Union[WebhookSubscriptionEdit, WebhookSubscriptionCreate]
] = None


class WebhookSubscriptionQueryRequest(BaseModel):
subscription: Optional[WebhookSubscriptionQuery] = None

Expand Down
135 changes: 69 additions & 66 deletions api/oss/src/apis/fastapi/webhooks/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@
)
from oss.src.utils.crypting import decrypt, encrypt
from oss.src.core.webhooks.service import WebhooksService
from oss.src.core.webhooks.types import WebhookSubscription
from oss.src.core.webhooks.types import WebhookSubscription, WebhookSubscriptionEdit
from oss.src.core.webhooks.exceptions import (
WebhookAuthorizationSecretRequiredError,
WebhookSubscriptionNotFoundError,
WebhookTestDeliveryTimeoutError,
WebhookTestEventPublishFailedError,
)
from oss.src.apis.fastapi.webhooks.models import (
WebhookSubscriptionCreateRequest,
WebhookSubscriptionTestRequest,
WebhookSubscriptionEditRequest,
WebhookSubscriptionQueryRequest,
WebhookSubscriptionResponse,
Expand Down Expand Up @@ -50,7 +49,7 @@ def __init__(
# --- WEBHOOK SUBSCRIPTIONS ------------------------------------------ #

self.router.add_api_route(
"/",
"/subscriptions/",
self.create_subscription,
methods=["POST"],
operation_id="create_webhook_subscription",
Expand All @@ -59,7 +58,16 @@ def __init__(
status_code=status.HTTP_200_OK,
)
self.router.add_api_route(
"/{subscription_id}",
"/subscriptions/test",
self.test_subscription,
methods=["POST"],
operation_id="test_webhook_subscription",
response_model=WebhookDeliveryResponse,
response_model_exclude_none=True,
status_code=status.HTTP_200_OK,
)
self.router.add_api_route(
"/subscriptions/{subscription_id}",
self.fetch_subscription,
methods=["GET"],
operation_id="fetch_webhook_subscription",
Expand All @@ -68,7 +76,7 @@ def __init__(
status_code=status.HTTP_200_OK,
)
self.router.add_api_route(
"/{subscription_id}",
"/subscriptions/{subscription_id}",
self.edit_subscription,
methods=["PUT"],
operation_id="edit_webhook_subscription",
Expand All @@ -77,14 +85,14 @@ def __init__(
status_code=status.HTTP_200_OK,
)
self.router.add_api_route(
"/{subscription_id}",
"/subscriptions/{subscription_id}",
self.delete_subscription,
methods=["DELETE"],
operation_id="delete_webhook_subscription",
status_code=status.HTTP_204_NO_CONTENT,
)
self.router.add_api_route(
"/query",
"/subscriptions/query",
self.query_subscriptions,
methods=["POST"],
operation_id="query_webhook_subscriptions",
Expand Down Expand Up @@ -123,18 +131,6 @@ def __init__(
status_code=status.HTTP_200_OK,
)

# --- WEBHOOK TEST --------------------------------------------------- #

self.router.add_api_route(
"/test/{subscription_id}",
self.test_webhook,
methods=["POST"],
operation_id="test_webhook",
response_model=WebhookDeliveryResponse,
response_model_exclude_none=True,
status_code=status.HTTP_200_OK,
)

# --- WEBHOOK SUBSCRIPTIONS ---------------------------------------------- #

@intercept_exceptions()
Expand Down Expand Up @@ -467,12 +463,11 @@ async def query_deliveries(

# --- WEBHOOK TESTS ------------------------------------------------------ #

@intercept_exceptions()
async def test_webhook(
async def _test_subscription_impl(
self,
request: Request,
*,
subscription_id: UUID,
request: Request,
body: WebhookSubscriptionTestRequest,
) -> WebhookDeliveryResponse:
if is_ee():
has_permission = await check_action_access(
Expand All @@ -483,52 +478,60 @@ async def test_webhook(
if not has_permission:
raise FORBIDDEN_EXCEPTION # type: ignore

try:
delivery = await self.webhooks_service.test_webhook(
if (body.subscription_id is None) == (body.subscription is None):
raise HTTPException(
status_code=400,
detail="Provide exactly one of subscription_id or subscription",
)

subscription = body.subscription

if subscription is None:
assert body.subscription_id is not None
existing = await self.webhooks_service.fetch_subscription(
project_id=UUID(request.state.project_id),
#
subscription_id=subscription_id,
subscription_id=body.subscription_id,
)

if delivery.status and delivery.status.message == "success":
await invalidate_cache(
namespace="webhooks",
project_id=str(request.state.project_id),
key=f"subscription:{subscription_id}",
)
await invalidate_cache(
namespace="webhooks",
project_id=str(request.state.project_id),
key="subscriptions",
if not existing:
raise HTTPException(
status_code=404, detail="Webhook subscription not found"
)

return WebhookDeliveryResponse(
count=1 if delivery else 0,
delivery=delivery,
subscription = WebhookSubscriptionEdit(
id=existing.id,
name=existing.name,
description=existing.description,
data=existing.data,
secret=existing.secret,
)

assert subscription is not None

try:
delivery = await self.webhooks_service.test_subscription(
project_id=UUID(request.state.project_id),
user_id=UUID(str(request.state.user_id)),
subscription=subscription,
)
except WebhookAuthorizationSecretRequiredError as e:
raise HTTPException(status_code=400, detail=e.message) from e
except WebhookSubscriptionNotFoundError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e),
) from e
except WebhookTestEventPublishFailedError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={
"code": "WEBHOOK_TEST_EVENT_PUBLISH_FAILED",
"message": e.message,
"event_id": e.event_id,
"subscription_id": e.subscription_id,
},
) from e
except WebhookTestDeliveryTimeoutError as e:
raise HTTPException(
status_code=status.HTTP_504_GATEWAY_TIMEOUT,
detail={
"code": "WEBHOOK_TEST_DELIVERY_TIMEOUT",
"message": e.message,
"event_id": e.event_id,
"subscription_id": e.subscription_id,
"attempts": e.attempts,
},
) from e
raise HTTPException(status_code=404, detail=str(e)) from e

return WebhookDeliveryResponse(
count=1 if delivery else 0,
delivery=delivery,
)

@intercept_exceptions()
async def test_subscription(
self,
request: Request,
*,
body: WebhookSubscriptionTestRequest,
) -> WebhookDeliveryResponse:
return await self._test_subscription_impl(
request=request,
body=body,
)
Loading
Loading