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}