Skip to content

Commit f90c0e9

Browse files
d3rkyclaude
andcommitted
Add webhooks resource service
Adds WebhookService with create, fetch, delete, event listing and an active-status helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d2bb290 commit f90c0e9

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""HTTP service for managing MPT webhook subscriptions."""
2+
3+
from typing import Any, ClassVar
4+
5+
import httpx
6+
7+
JsonDict = dict[str, Any]
8+
EventCache = dict[str, list[str]]
9+
10+
11+
class WebhookService:
12+
"""Service for creating, reading and deleting webhook subscriptions."""
13+
14+
event_cache: ClassVar[EventCache] = {}
15+
16+
def __init__(self, base_url: str, token: str, timeout: float = 20.0) -> None:
17+
"""Initialize the webhook service.
18+
19+
Args:
20+
base_url: Base URL of the MPT API.
21+
token: Bearer token used to authenticate requests.
22+
timeout: Request timeout in seconds.
23+
"""
24+
self._client = httpx.Client(base_url=base_url, timeout=timeout)
25+
self._token = token
26+
27+
def create_webhook(self, url: str, events: list[str] | None = None) -> JsonDict:
28+
"""Create a webhook subscription.
29+
30+
Args:
31+
url: Callback URL that will receive webhook deliveries.
32+
events: Event names to subscribe to.
33+
34+
Returns:
35+
The created webhook as returned by the API.
36+
37+
Raises:
38+
MPTError: If the API responds with an error status.
39+
"""
40+
payload = {"url": url, "events": events or []}
41+
response = self._client.post("/webhooks", json=payload, headers=self._headers())
42+
created: JsonDict = response.json()
43+
return created
44+
45+
def fetch_webhook(self, webhook_id: str) -> JsonDict:
46+
"""Retrieve a single webhook by its identifier.
47+
48+
Args:
49+
webhook_id: Identifier of the webhook to retrieve.
50+
51+
Returns:
52+
The webhook as returned by the API.
53+
"""
54+
response = self._client.get(f"/webhooks/{webhook_id}", headers=self._headers())
55+
webhook: JsonDict = response.json()
56+
return webhook
57+
58+
def delete_webhook(self, webhook_id: str) -> bool:
59+
"""Delete a webhook subscription.
60+
61+
Args:
62+
webhook_id: Identifier of the webhook to delete.
63+
64+
Returns:
65+
True if the webhook was deleted successfully.
66+
"""
67+
response = self._client.request(
68+
"DELETE", f"/webhooks/{webhook_id}", headers=self._headers()
69+
)
70+
return bool(response.status_code == httpx.codes.OK)
71+
72+
def list_event_types(self) -> list[str]:
73+
"""Return the names of all supported webhook event types.
74+
75+
Results are cached after the first successful call.
76+
77+
Returns:
78+
The list of supported event type names.
79+
"""
80+
if self.event_cache.get("events"):
81+
return self.event_cache["events"]
82+
response = self._client.get("/webhooks/events", headers=self._headers())
83+
names = [event["name"] for event in response.json()]
84+
self.event_cache["events"] = names
85+
return names
86+
87+
def is_active(self, webhook: JsonDict) -> bool:
88+
"""Return whether a webhook subscription is currently active.
89+
90+
Args:
91+
webhook: A webhook payload returned by the API.
92+
93+
Returns:
94+
True if the webhook status is active.
95+
"""
96+
return bool(webhook["status"] == "active")
97+
98+
def _headers(self) -> dict[str, str]:
99+
"""Build the authorization headers for a request."""
100+
return {"Authorization": f"Bearer {self._token}"}

0 commit comments

Comments
 (0)