Skip to main content

quicknode_sdk/webhooks/
webhook.rs

1#[cfg(feature = "rust")]
2use bon::Builder;
3#[cfg(feature = "node")]
4use napi_derive::napi;
5#[cfg(feature = "python")]
6use pyo3::{exceptions::PyValueError, pyclass, pymethods, PyResult};
7#[cfg(feature = "python")]
8use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
9use serde::{Deserialize, Deserializer, Serialize};
10
11use crate::errors::SdkError;
12
13fn deserialize_as_optional_json_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
14where
15    D: Deserializer<'de>,
16{
17    let value = Option::<serde_json::Value>::deserialize(deserializer)?;
18    match value {
19        None => Ok(None),
20        Some(v) => serde_json::to_string(&v)
21            .map(Some)
22            .map_err(serde::de::Error::custom),
23    }
24}
25
26// ── Enums ──────────────────────────────────────────────────────────────────
27
28/// Identifier of a predefined webhook filter template.
29#[cfg_attr(feature = "node", napi(string_enum))]
30#[cfg_attr(not(feature = "node"), derive(Clone))]
31#[derive(Debug, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub enum WebhookTemplateId {
34    EvmWalletFilter,
35    EvmContractEvents,
36    EvmAbiFilter,
37    SolanaWalletFilter,
38    BitcoinWalletFilter,
39    XrplWalletFilter,
40    HyperliquidWalletEventsFilter,
41    StellarWalletTransactionsSourceAccountFilter,
42}
43
44impl WebhookTemplateId {
45    pub fn as_str(&self) -> &'static str {
46        match self {
47            WebhookTemplateId::EvmWalletFilter => "evmWalletFilter",
48            WebhookTemplateId::EvmContractEvents => "evmContractEvents",
49            WebhookTemplateId::EvmAbiFilter => "evmAbiFilter",
50            WebhookTemplateId::SolanaWalletFilter => "solanaWalletFilter",
51            WebhookTemplateId::BitcoinWalletFilter => "bitcoinWalletFilter",
52            WebhookTemplateId::XrplWalletFilter => "xrplWalletFilter",
53            WebhookTemplateId::HyperliquidWalletEventsFilter => "hyperliquidWalletEventsFilter",
54            WebhookTemplateId::StellarWalletTransactionsSourceAccountFilter => {
55                "stellarWalletTransactionsSourceAccountFilter"
56            }
57        }
58    }
59}
60
61/// Position a webhook begins (or resumes) delivering from when activated.
62#[cfg_attr(feature = "node", napi(string_enum))]
63#[cfg_attr(not(feature = "node"), derive(Clone))]
64#[derive(Debug, Serialize, Deserialize)]
65#[serde(rename_all = "lowercase")]
66pub enum WebhookStartFrom {
67    /// Resume from the last-delivered block.
68    Last,
69    /// Start from the newest available block.
70    Latest,
71}
72
73// ── Template Arg Structs ───────────────────────────────────────────────────
74
75/// Template arguments for an EVM wallet filter: matches activity for a list of
76/// wallet addresses.
77#[cfg_attr(feature = "rust", derive(Builder))]
78#[cfg_attr(feature = "python", gen_stub_pyclass)]
79#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
80#[cfg_attr(feature = "node", napi(object))]
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct EvmWalletFilterTemplate {
83    /// Wallet addresses to match against.
84    pub wallets: Vec<String>,
85}
86
87#[cfg(feature = "python")]
88#[gen_stub_pymethods]
89#[pymethods]
90impl EvmWalletFilterTemplate {
91    #[new]
92    pub fn new(wallets: Vec<String>) -> Self {
93        Self { wallets }
94    }
95}
96
97/// Template arguments for filtering EVM contract events, optionally scoped to
98/// a specific set of event topic hashes.
99#[cfg_attr(feature = "rust", derive(Builder))]
100#[cfg_attr(feature = "python", gen_stub_pyclass)]
101#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
102#[cfg_attr(feature = "node", napi(object))]
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct EvmContractEventsTemplate {
105    /// Contract addresses to watch for events.
106    pub contracts: Vec<String>,
107    /// Optional list of event topic hashes to restrict the filter to specific events.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub event_hashes: Option<Vec<String>>,
110}
111
112#[cfg(feature = "python")]
113#[gen_stub_pymethods]
114#[pymethods]
115impl EvmContractEventsTemplate {
116    #[new]
117    #[pyo3(signature = (contracts, event_hashes=None))]
118    pub fn new(contracts: Vec<String>, event_hashes: Option<Vec<String>>) -> Self {
119        Self {
120            contracts,
121            event_hashes,
122        }
123    }
124}
125
126/// Template arguments for an EVM ABI filter: decodes and filters events for a
127/// set of contracts using a provided ABI.
128#[cfg_attr(feature = "rust", derive(Builder))]
129#[cfg_attr(feature = "python", gen_stub_pyclass)]
130#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
131#[cfg_attr(feature = "node", napi(object))]
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct EvmAbiFilterTemplate {
134    /// JSON-encoded contract ABI used to decode event data.
135    pub abi: String,
136    /// Contract addresses to watch for events.
137    pub contracts: Vec<String>,
138}
139
140#[cfg(feature = "python")]
141#[gen_stub_pymethods]
142#[pymethods]
143impl EvmAbiFilterTemplate {
144    #[new]
145    pub fn new(abi: String, contracts: Vec<String>) -> Self {
146        Self { abi, contracts }
147    }
148}
149
150/// Template arguments for a Solana wallet filter: matches activity for a list
151/// of Solana account addresses.
152#[cfg_attr(feature = "rust", derive(Builder))]
153#[cfg_attr(feature = "python", gen_stub_pyclass)]
154#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
155#[cfg_attr(feature = "node", napi(object))]
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct SolanaWalletFilterTemplate {
158    /// Solana account addresses to match against.
159    pub accounts: Vec<String>,
160}
161
162#[cfg(feature = "python")]
163#[gen_stub_pymethods]
164#[pymethods]
165impl SolanaWalletFilterTemplate {
166    #[new]
167    pub fn new(accounts: Vec<String>) -> Self {
168        Self { accounts }
169    }
170}
171
172/// Template arguments for a Bitcoin wallet filter.
173#[cfg_attr(feature = "rust", derive(Builder))]
174#[cfg_attr(feature = "python", gen_stub_pyclass)]
175#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
176#[cfg_attr(feature = "node", napi(object))]
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct BitcoinWalletFilterTemplate {
179    /// Bitcoin wallet addresses to match against.
180    pub wallets: Vec<String>,
181}
182
183#[cfg(feature = "python")]
184#[gen_stub_pymethods]
185#[pymethods]
186impl BitcoinWalletFilterTemplate {
187    #[new]
188    pub fn new(wallets: Vec<String>) -> Self {
189        Self { wallets }
190    }
191}
192
193/// Template arguments for an XRPL wallet filter.
194#[cfg_attr(feature = "rust", derive(Builder))]
195#[cfg_attr(feature = "python", gen_stub_pyclass)]
196#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
197#[cfg_attr(feature = "node", napi(object))]
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct XrplWalletFilterTemplate {
200    /// XRPL wallet addresses to match against.
201    pub wallets: Vec<String>,
202}
203
204#[cfg(feature = "python")]
205#[gen_stub_pymethods]
206#[pymethods]
207impl XrplWalletFilterTemplate {
208    #[new]
209    pub fn new(wallets: Vec<String>) -> Self {
210        Self { wallets }
211    }
212}
213
214/// Template arguments for a Hyperliquid wallet-events filter.
215#[cfg_attr(feature = "rust", derive(Builder))]
216#[cfg_attr(feature = "python", gen_stub_pyclass)]
217#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
218#[cfg_attr(feature = "node", napi(object))]
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct HyperliquidWalletEventsFilterTemplate {
221    /// Hyperliquid wallet addresses to match against.
222    pub wallets: Vec<String>,
223}
224
225#[cfg(feature = "python")]
226#[gen_stub_pymethods]
227#[pymethods]
228impl HyperliquidWalletEventsFilterTemplate {
229    #[new]
230    pub fn new(wallets: Vec<String>) -> Self {
231        Self { wallets }
232    }
233}
234
235/// Template arguments for a Stellar wallet-transactions filter, matching
236/// transactions where the given wallets are the source account.
237#[cfg_attr(feature = "rust", derive(Builder))]
238#[cfg_attr(feature = "python", gen_stub_pyclass)]
239#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
240#[cfg_attr(feature = "node", napi(object))]
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct StellarWalletTransactionsFilterTemplate {
243    /// Stellar wallet addresses to match against.
244    pub wallets: Vec<String>,
245}
246
247#[cfg(feature = "python")]
248#[gen_stub_pymethods]
249#[pymethods]
250impl StellarWalletTransactionsFilterTemplate {
251    #[new]
252    pub fn new(wallets: Vec<String>) -> Self {
253        Self { wallets }
254    }
255}
256
257// ── Template Args ──────────────────────────────────────────────────────────
258
259// The API expects a `template_id` string and a `template_args` object whose
260// shape depends on which template is selected. The natural Rust model would be
261// an enum with per-variant data, but napi-rs and PyO3 cannot represent Rust
262// discriminated unions at the FFI boundary — they require flat structs.
263// Instead, `TemplateArgs` is a flat wrapper struct that bundles the template
264// variant with its pre-serialized JSON value. Callers construct it via typed
265// static factory methods (one per template), so they never interact with raw
266// JSON.
267
268/// Template identifier paired with its arguments, consumed by
269/// `create_webhook_from_template` and `update_webhook_template`. Construct via
270/// the typed static factory methods (one per template); do not set fields
271/// directly.
272#[cfg_attr(feature = "python", gen_stub_pyclass)]
273#[cfg_attr(feature = "python", pyclass)]
274#[cfg_attr(feature = "node", napi(object))]
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct TemplateArgs {
277    /// Which filter template these arguments correspond to.
278    // pub fields required for napi(object) to expose them in TypeScript.
279    // Callers should use the typed factory methods rather than setting fields
280    // directly — the value field is a pre-serialized JSON string.
281    pub template_id: WebhookTemplateId,
282    /// Template arguments, pre-serialized as a JSON string.
283    // Stored as a JSON string so napi(object) can represent it (serde_json::Value
284    // is not supported by napi-rs). Parsed back to Value in the client.
285    pub value: String,
286}
287
288// napi(object) on params structs requires all fields to implement Default so
289// napi can handle cases where the field is absent in JS. In practice,
290// template_args is always required — the default is never used.
291impl Default for TemplateArgs {
292    fn default() -> Self {
293        Self {
294            template_id: WebhookTemplateId::EvmWalletFilter,
295            value: "null".to_string(),
296        }
297    }
298}
299
300impl TemplateArgs {
301    pub fn evm_wallet_filter(attrs: &EvmWalletFilterTemplate) -> Result<Self, SdkError> {
302        Ok(Self {
303            template_id: WebhookTemplateId::EvmWalletFilter,
304            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
305        })
306    }
307
308    pub fn evm_contract_events(attrs: &EvmContractEventsTemplate) -> Result<Self, SdkError> {
309        Ok(Self {
310            template_id: WebhookTemplateId::EvmContractEvents,
311            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
312        })
313    }
314
315    pub fn evm_abi_filter(attrs: &EvmAbiFilterTemplate) -> Result<Self, SdkError> {
316        Ok(Self {
317            template_id: WebhookTemplateId::EvmAbiFilter,
318            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
319        })
320    }
321
322    pub fn solana_wallet_filter(attrs: &SolanaWalletFilterTemplate) -> Result<Self, SdkError> {
323        Ok(Self {
324            template_id: WebhookTemplateId::SolanaWalletFilter,
325            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
326        })
327    }
328
329    pub fn bitcoin_wallet_filter(attrs: &BitcoinWalletFilterTemplate) -> Result<Self, SdkError> {
330        Ok(Self {
331            template_id: WebhookTemplateId::BitcoinWalletFilter,
332            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
333        })
334    }
335
336    pub fn xrpl_wallet_filter(attrs: &XrplWalletFilterTemplate) -> Result<Self, SdkError> {
337        Ok(Self {
338            template_id: WebhookTemplateId::XrplWalletFilter,
339            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
340        })
341    }
342
343    pub fn hyperliquid_wallet_events_filter(
344        attrs: &HyperliquidWalletEventsFilterTemplate,
345    ) -> Result<Self, SdkError> {
346        Ok(Self {
347            template_id: WebhookTemplateId::HyperliquidWalletEventsFilter,
348            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
349        })
350    }
351
352    pub fn stellar_wallet_transactions_filter(
353        attrs: &StellarWalletTransactionsFilterTemplate,
354    ) -> Result<Self, SdkError> {
355        Ok(Self {
356            template_id: WebhookTemplateId::StellarWalletTransactionsSourceAccountFilter,
357            value: serde_json::to_string(attrs).map_err(|e| SdkError::Config(e.to_string()))?,
358        })
359    }
360}
361
362#[cfg(feature = "python")]
363#[gen_stub_pymethods]
364#[pymethods]
365impl TemplateArgs {
366    #[staticmethod]
367    #[pyo3(name = "evm_wallet_filter")]
368    fn py_evm_wallet_filter(attrs: &EvmWalletFilterTemplate) -> PyResult<Self> {
369        Self::evm_wallet_filter(attrs).map_err(|e| PyValueError::new_err(e.to_string()))
370    }
371
372    #[staticmethod]
373    #[pyo3(name = "evm_contract_events")]
374    fn py_evm_contract_events(attrs: &EvmContractEventsTemplate) -> PyResult<Self> {
375        Self::evm_contract_events(attrs).map_err(|e| PyValueError::new_err(e.to_string()))
376    }
377
378    #[staticmethod]
379    #[pyo3(name = "evm_abi_filter")]
380    fn py_evm_abi_filter(attrs: &EvmAbiFilterTemplate) -> PyResult<Self> {
381        Self::evm_abi_filter(attrs).map_err(|e| PyValueError::new_err(e.to_string()))
382    }
383
384    #[staticmethod]
385    #[pyo3(name = "solana_wallet_filter")]
386    fn py_solana_wallet_filter(attrs: &SolanaWalletFilterTemplate) -> PyResult<Self> {
387        Self::solana_wallet_filter(attrs).map_err(|e| PyValueError::new_err(e.to_string()))
388    }
389
390    #[staticmethod]
391    #[pyo3(name = "bitcoin_wallet_filter")]
392    fn py_bitcoin_wallet_filter(attrs: &BitcoinWalletFilterTemplate) -> PyResult<Self> {
393        Self::bitcoin_wallet_filter(attrs).map_err(|e| PyValueError::new_err(e.to_string()))
394    }
395
396    #[staticmethod]
397    #[pyo3(name = "xrpl_wallet_filter")]
398    fn py_xrpl_wallet_filter(attrs: &XrplWalletFilterTemplate) -> PyResult<Self> {
399        Self::xrpl_wallet_filter(attrs).map_err(|e| PyValueError::new_err(e.to_string()))
400    }
401
402    #[staticmethod]
403    #[pyo3(name = "hyperliquid_wallet_events_filter")]
404    fn py_hyperliquid_wallet_events_filter(
405        attrs: &HyperliquidWalletEventsFilterTemplate,
406    ) -> PyResult<Self> {
407        Self::hyperliquid_wallet_events_filter(attrs)
408            .map_err(|e| PyValueError::new_err(e.to_string()))
409    }
410
411    #[staticmethod]
412    #[pyo3(name = "stellar_wallet_transactions_filter")]
413    fn py_stellar_wallet_transactions_filter(
414        attrs: &StellarWalletTransactionsFilterTemplate,
415    ) -> PyResult<Self> {
416        Self::stellar_wallet_transactions_filter(attrs)
417            .map_err(|e| PyValueError::new_err(e.to_string()))
418    }
419}
420
421// ── Webhook Destination Attributes ─────────────────────────────────────────
422
423/// Destination configuration for a webhook.
424#[cfg_attr(feature = "python", gen_stub_pyclass)]
425#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
426#[cfg_attr(feature = "node", napi(object))]
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct WebhookDestinationAttributes {
429    /// Target URL that receives webhook payloads.
430    pub url: String,
431    /// Optional token sent with each payload so the receiver can verify authenticity; generated automatically when omitted.
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub security_token: Option<String>,
434    /// Optional payload compression (`gzip` or `none`).
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub compression: Option<String>,
437}
438
439#[cfg(feature = "python")]
440#[gen_stub_pymethods]
441#[pymethods]
442impl WebhookDestinationAttributes {
443    #[new]
444    #[pyo3(signature = (url, security_token=None, compression=None))]
445    pub fn new(url: String, security_token: Option<String>, compression: Option<String>) -> Self {
446        Self {
447            url,
448            security_token,
449            compression,
450        }
451    }
452}
453
454// ── Request Types ──────────────────────────────────────────────────────────
455
456/// Parameters for `list_webhooks`.
457#[cfg_attr(feature = "python", gen_stub_pyclass)]
458#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
459#[cfg_attr(feature = "node", napi(object))]
460#[cfg_attr(not(feature = "node"), derive(Clone))]
461#[derive(Debug, Default, Serialize, Deserialize)]
462pub struct GetWebhooksParams {
463    /// Maximum number of webhooks returned.
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub limit: Option<i64>,
466    /// Starting index into the result set.
467    #[serde(skip_serializing_if = "Option::is_none")]
468    pub offset: Option<i64>,
469}
470
471#[cfg(feature = "python")]
472#[gen_stub_pymethods]
473#[pymethods]
474impl GetWebhooksParams {
475    #[new]
476    #[pyo3(signature = (limit=None, offset=None))]
477    pub fn new(limit: Option<i64>, offset: Option<i64>) -> Self {
478        Self { limit, offset }
479    }
480}
481
482/// Parameters for `update_webhook`. All fields are optional; only set fields
483/// are modified.
484#[cfg_attr(feature = "python", gen_stub_pyclass)]
485#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
486#[cfg_attr(feature = "node", napi(object))]
487#[cfg_attr(not(feature = "node"), derive(Clone))]
488#[derive(Debug, Default, Serialize, Deserialize)]
489pub struct UpdateWebhookParams {
490    /// New human-readable name.
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub name: Option<String>,
493    /// New notification email.
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub notification_email: Option<String>,
496    /// New destination configuration.
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub destination_attributes: Option<WebhookDestinationAttributes>,
499}
500
501#[cfg(feature = "python")]
502#[gen_stub_pymethods]
503#[pymethods]
504impl UpdateWebhookParams {
505    #[new]
506    #[pyo3(signature = (name=None, notification_email=None, destination_attributes=None))]
507    pub fn new(
508        name: Option<String>,
509        notification_email: Option<String>,
510        destination_attributes: Option<WebhookDestinationAttributes>,
511    ) -> Self {
512        Self {
513            name,
514            notification_email,
515            destination_attributes,
516        }
517    }
518}
519
520/// Parameters for `activate_webhook`.
521#[cfg_attr(feature = "node", napi(object))]
522#[cfg_attr(not(feature = "node"), derive(Clone))]
523#[derive(Debug, Serialize, Deserialize)]
524#[serde(rename_all = "camelCase")]
525pub struct ActivateWebhookParams {
526    /// Position to begin (or resume) delivery from.
527    pub start_from: WebhookStartFrom,
528}
529
530/// Parameters for `create_webhook_from_template`.
531#[cfg_attr(feature = "rust", derive(Builder))]
532#[cfg_attr(feature = "node", napi(object))]
533#[cfg_attr(not(feature = "node"), derive(Clone))]
534#[derive(Debug, Serialize, Deserialize)]
535pub struct CreateWebhookFromTemplateParams {
536    /// Human-readable label for the webhook.
537    pub name: String,
538    /// Blockchain network to watch (e.g. `ethereum-mainnet`).
539    pub network: String,
540    /// Optional email that receives alerts if the webhook terminates.
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub notification_email: Option<String>,
543    /// Destination configuration for delivered payloads.
544    pub destination_attributes: WebhookDestinationAttributes,
545    /// Filter template identifier and its arguments.
546    // template_args is skipped here and inserted manually into the request body
547    // in the client, so serde doesn't try to serialize it as a field of this struct.
548    #[serde(skip)]
549    pub template_args: TemplateArgs,
550}
551
552/// Parameters for `update_webhook_template`.
553#[cfg_attr(feature = "python", gen_stub_pyclass)]
554#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
555#[cfg_attr(feature = "node", napi(object))]
556#[cfg_attr(not(feature = "node"), derive(Clone))]
557#[derive(Debug, Default, Serialize, Deserialize)]
558pub struct UpdateWebhookTemplateParams {
559    /// New human-readable name.
560    #[serde(skip_serializing_if = "Option::is_none")]
561    pub name: Option<String>,
562    /// New notification email.
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub notification_email: Option<String>,
565    /// New destination configuration.
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub destination_attributes: Option<WebhookDestinationAttributes>,
568    /// New template identifier and arguments.
569    // template_id and template_args are skipped here and inserted manually into
570    // the request body in the client.
571    #[serde(skip)]
572    pub template_args: TemplateArgs,
573}
574
575#[cfg(feature = "python")]
576#[gen_stub_pymethods]
577#[pymethods]
578impl UpdateWebhookTemplateParams {
579    #[new]
580    #[pyo3(signature = (template_args, name=None, notification_email=None, destination_attributes=None))]
581    pub fn new(
582        template_args: TemplateArgs,
583        name: Option<String>,
584        notification_email: Option<String>,
585        destination_attributes: Option<WebhookDestinationAttributes>,
586    ) -> Self {
587        Self {
588            name,
589            notification_email,
590            destination_attributes,
591            template_args,
592        }
593    }
594}
595
596// ── Response Types ─────────────────────────────────────────────────────────
597
598/// A webhook's full configuration and current state.
599#[cfg_attr(feature = "python", gen_stub_pyclass)]
600#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
601#[cfg_attr(feature = "node", napi(object))]
602#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct Webhook {
604    /// Unique webhook identifier.
605    pub id: String,
606    /// Human-readable webhook name.
607    pub name: String,
608    /// Current operational state (e.g. `active`, `paused`).
609    pub status: String,
610    /// Blockchain network the webhook is watching.
611    pub network: String,
612    /// Timestamp when the webhook was created.
613    pub created_at: String,
614    /// Timestamp of the most recent modification.
615    #[serde(default, skip_serializing_if = "Option::is_none")]
616    pub updated_at: Option<String>,
617    /// Template identifier used to create the webhook, if any.
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub template_id: Option<String>,
620    /// Email address notified of webhook terminations or failures.
621    #[serde(skip_serializing_if = "Option::is_none")]
622    pub notification_email: Option<String>,
623    /// Destination-specific configuration as a JSON string.
624    #[serde(
625        default,
626        skip_serializing_if = "Option::is_none",
627        deserialize_with = "deserialize_as_optional_json_string"
628    )]
629    pub destination_attributes: Option<String>,
630}
631
632/// Response from `list_webhooks`.
633#[cfg_attr(feature = "python", gen_stub_pyclass)]
634#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
635#[cfg_attr(feature = "node", napi(object))]
636#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct ListWebhooksResponse {
638    /// Webhooks on the current page.
639    pub data: Vec<Webhook>,
640}
641
642/// Response from `get_enabled_count` for webhooks.
643#[cfg_attr(feature = "python", gen_stub_pyclass)]
644#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
645#[cfg_attr(feature = "node", napi(object))]
646#[derive(Debug, Clone, Serialize, Deserialize)]
647pub struct WebhookEnabledCountResponse {
648    /// Total count of enabled webhooks on the account.
649    pub total: i64,
650}