Skip to main content

uarp_sdk/generated/api/
webhooks.rs

1// Code generated by @uarp/codegen from spec/openapi.json. DO NOT EDIT.
2//!
3//! Webhook subscriptions
4
5#![allow(unused_imports, clippy::too_many_arguments)]
6
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9
10use crate::client::{Client, Request, NO_BODY, NO_QUERY};
11use crate::error::Result;
12use crate::generated::models;
13use crate::multipart::{field_text, FilePart};
14use crate::util::encode_path;
15
16/// Query and header parameters for `sensorWebhook`.
17#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
18pub struct SensorWebhookParams {
19    /// Hex-encoded HMAC-SHA256 of the request body using the subscription's signing secret.
20    #[serde(skip)]
21    pub x_sensor_signature: String,
22}
23
24/// Webhook subscriptions
25#[derive(Debug, Clone)]
26pub struct WebhooksApi {
27    pub(crate) client: Client,
28}
29
30impl Client {
31    /// Webhook subscriptions
32    pub fn webhooks(&self) -> WebhooksApi {
33        WebhooksApi { client: self.clone() }
34    }
35}
36
37impl WebhooksApi {
38    /// Create a webhook subscription
39    ///
40    /// Registers a receiver for platform events. `url` and a non-empty `events` array are required,
41    /// and every event name must be one of the fixed enumeration (`run.completed`,
42    /// `quota.exceeded`, `company.budget_alert` and the rest); anything else fails body validation.
43    /// The URL is screened for SSRF before registration — with DNS resolution when the admin
44    /// webhooks policy enables the subscription-time check, with static checks plus the admin
45    /// denylist otherwise — and HTTPS is required when the deployment configures it. The tenant's
46    /// subscription cap is enforced. The signing secret is minted server-side and returned in full
47    /// only on this 201 response; every later read redacts it.
48    ///
49    /// `POST /api/v1/webhooks`
50    ///
51    /// Required scopes: `webhooks:write`.
52    pub async fn create(&self, body: &models::CreateWebhookRequest) -> Result<models::WebhookSubscription> {
53        self.client
54            .request_json(Request {
55                method: Method::POST,
56                path: "/api/v1/webhooks".to_string(),
57                query: NO_QUERY,
58                body: Some(body),
59                headers: Vec::new(),
60                idempotent: true,
61            })
62            .await
63    }
64
65    /// Delete a webhook subscription
66    ///
67    /// Removes the subscription so it stops receiving events. The delete is unconditional — no
68    /// existence check runs first — so it is idempotent and answers 200 with `deleted: true` even
69    /// for an id the tenant never had. Past deliveries are not removed.
70    ///
71    /// `DELETE /api/v1/webhooks/{webhookId}`
72    ///
73    /// Required scopes: `webhooks:write`.
74    pub async fn delete(&self, webhook_id: &str) -> Result<models::DeleteWebhookResponse> {
75        self.client
76            .request_json(Request {
77                method: Method::DELETE,
78                path: format!("/api/v1/webhooks/{}", encode_path(webhook_id)),
79                query: NO_QUERY,
80                body: NO_BODY,
81                headers: Vec::new(),
82                idempotent: true,
83            })
84            .await
85    }
86
87    /// Get a webhook subscription
88    ///
89    /// Returns one subscription, found by scanning the tenant's list, with its signing secret
90    /// redacted. 404 when the tenant has no subscription with that id.
91    ///
92    /// `GET /api/v1/webhooks/{webhookId}`
93    ///
94    /// Required scopes: `webhooks:read`.
95    pub async fn get(&self, webhook_id: &str) -> Result<models::WebhookSubscription> {
96        self.client
97            .request_json(Request {
98                method: Method::GET,
99                path: format!("/api/v1/webhooks/{}", encode_path(webhook_id)),
100                query: NO_QUERY,
101                body: NO_BODY,
102                headers: Vec::new(),
103                idempotent: false,
104            })
105            .await
106    }
107
108    /// List webhook subscriptions
109    ///
110    /// Lists the tenant's webhook subscriptions with their URL, event list, status and failure
111    /// count. Each subscription's signing secret comes back as the literal `\[redacted\]` — the
112    /// real value is only ever returned by the create call.
113    ///
114    /// `GET /api/v1/webhooks`
115    ///
116    /// Required scopes: `webhooks:read`.
117    pub async fn list(&self) -> Result<models::ListWebhooksResponse> {
118        self.client
119            .request_json(Request {
120                method: Method::GET,
121                path: "/api/v1/webhooks".to_string(),
122                query: NO_QUERY,
123                body: NO_BODY,
124                headers: Vec::new(),
125                idempotent: false,
126            })
127            .await
128    }
129
130    /// List webhook deliveries
131    ///
132    /// The delivery log for one subscription, newest first. Delivery rows are stored per tenant
133    /// rather than per webhook, so the read is a bounded reverse scan — up to 5 000 rows walked —
134    /// filtered to this webhook and capped at 100 in the response. `truncated` is true when the
135    /// scan bound or the page cap was reached, which is what distinguishes "this webhook has no
136    /// more deliveries" from "the scan stopped"; `total` counts only the rows in this response.
137    /// Rows carry a 7-day TTL, so older deliveries are simply gone.
138    ///
139    /// `GET /api/v1/webhooks/{webhookId}/deliveries`
140    ///
141    /// Required scopes: `webhooks:read`.
142    pub async fn list_webhook_deliveries(&self, webhook_id: &str) -> Result<models::ListWebhookDeliveriesResponse> {
143        self.client
144            .request_json(Request {
145                method: Method::GET,
146                path: format!("/api/v1/webhooks/{}/deliveries", encode_path(webhook_id)),
147                query: NO_QUERY,
148                body: NO_BODY,
149                headers: Vec::new(),
150                idempotent: false,
151            })
152            .await
153    }
154
155    /// Sensor webhook (HMAC-authenticated, triggers an agent run)
156    ///
157    /// External-system webhook. Bypasses normal auth — `X-Sensor-Signature` header is HMAC-verified
158    /// against the per-subscription secret. On valid signature, fires an agent run with the request
159    /// body as input.
160    ///
161    /// `POST /api/v1/webhooks/sensor/{webhookId}`
162    pub async fn sensor_webhook(&self, webhook_id: &str, body: &serde_json::Map<String, serde_json::Value>, params: &SensorWebhookParams) -> Result<models::SensorWebhookResponse> {
163        let mut headers: Vec<(&'static str, String)> = Vec::new();
164        headers.push(("X-Sensor-Signature", params.x_sensor_signature.clone()));
165        self.client
166            .request_json(Request {
167                method: Method::POST,
168                path: format!("/api/v1/webhooks/sensor/{}", encode_path(webhook_id)),
169                query: NO_QUERY,
170                body: Some(body),
171                headers,
172                idempotent: true,
173            })
174            .await
175    }
176
177    /// Send test delivery
178    ///
179    /// Dispatch one synthetic event to this subscription's receiver, so an operator can confirm the
180    /// endpoint works before relying on it.
181    ///
182    /// Only an **active** subscription is delivered to — `WebhookManager.dispatch` filters on
183    /// status and silently matches nothing otherwise. A disabled subscription therefore answers
184    /// **422**, not 200: until 2026-08-20 it answered `{"test_sent": true}` with nothing sent, no
185    /// delivery row written and no dispatch line in the server log, which turns a misconfigured
186    /// integration into a confirmed one.
187    ///
188    /// `POST /api/v1/webhooks/{webhookId}/test`
189    ///
190    /// Required scopes: `webhooks:write`.
191    pub async fn test_webhook(&self, webhook_id: &str) -> Result<models::TestWebhookResponse> {
192        self.client
193            .request_json(Request {
194                method: Method::POST,
195                path: format!("/api/v1/webhooks/{}/test", encode_path(webhook_id)),
196                query: NO_QUERY,
197                body: NO_BODY,
198                headers: Vec::new(),
199                idempotent: true,
200            })
201            .await
202    }
203}