1pub mod webhook;
2
3pub use webhook::{
4 ActivateWebhookParams, BitcoinWalletFilterTemplate, CreateWebhookFromTemplateParams,
5 EvmAbiFilterTemplate, EvmContractEventsTemplate, EvmWalletFilterTemplate, GetWebhooksParams,
6 HyperliquidWalletEventsFilterTemplate, ListWebhooksResponse, SolanaWalletFilterTemplate,
7 StellarWalletTransactionsFilterTemplate, TemplateArgs, UpdateWebhookParams,
8 UpdateWebhookTemplateParams, Webhook, WebhookDestinationAttributes,
9 WebhookEnabledCountResponse, WebhookPageInfo, WebhookStartFrom, WebhookTemplateId,
10 XrplWalletFilterTemplate,
11};
12
13use crate::{config::WebhooksConfig, errors::SdkError, SdkConfig};
14
15const WEBHOOKS_BASE_URL: &str = "https://api.quicknode.com/webhooks/rest/v1/";
16
17pub(crate) struct ResolvedWebhooksConfig {
18 pub(crate) base_url: reqwest::Url,
19}
20
21impl ResolvedWebhooksConfig {
22 pub(crate) fn from_config(config: Option<&WebhooksConfig>) -> Result<Self, SdkError> {
23 let url_str = config
24 .and_then(|s| s.base_url.as_deref())
25 .unwrap_or(WEBHOOKS_BASE_URL);
26 let mut base_url = reqwest::Url::parse(url_str)?;
27 if !base_url.path().ends_with('/') {
28 base_url.set_path(&format!("{}/", base_url.path()));
29 }
30 Ok(Self { base_url })
31 }
32}
33
34#[derive(Debug, Clone)]
39pub struct WebhooksApiClient {
40 config: SdkConfig,
41}
42
43impl WebhooksApiClient {
44 pub fn new(config: SdkConfig) -> Self {
45 Self { config }
46 }
47
48 pub async fn list_webhooks(
54 &self,
55 params: &GetWebhooksParams,
56 ) -> Result<ListWebhooksResponse, SdkError> {
57 let mut url = self.config.webhooks().base_url.join("webhooks")?;
58 {
59 let mut pairs = url.query_pairs_mut();
60 if let Some(v) = params.limit {
61 pairs.append_pair("limit", &v.to_string());
62 }
63 if let Some(v) = params.offset {
64 pairs.append_pair("offset", &v.to_string());
65 }
66 }
67 let resp = self
68 .config
69 .http_client()
70 .get(url)
71 .send()
72 .await
73 .map_err(SdkError::Http)?;
74 let status = resp.status();
75 let body = resp.text().await.map_err(SdkError::Http)?;
76 if !status.is_success() {
77 return Err(SdkError::Api { status, body });
78 }
79 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
80 }
81
82 pub async fn delete_all_webhooks(&self) -> Result<(), SdkError> {
85 let url = self.config.webhooks().base_url.join("webhooks")?;
86 let resp = self
87 .config
88 .http_client()
89 .delete(url)
90 .send()
91 .await
92 .map_err(SdkError::Http)?;
93 let status = resp.status();
94 if !status.is_success() {
95 let body = resp.text().await.map_err(SdkError::Http)?;
96 return Err(SdkError::Api { status, body });
97 }
98 Ok(())
99 }
100
101 pub async fn get_webhook(&self, id: &str) -> Result<Webhook, SdkError> {
107 let url = self
108 .config
109 .webhooks()
110 .base_url
111 .join(&format!("webhooks/{id}"))?;
112 let resp = self
113 .config
114 .http_client()
115 .get(url)
116 .send()
117 .await
118 .map_err(SdkError::Http)?;
119 let status = resp.status();
120 let body = resp.text().await.map_err(SdkError::Http)?;
121 if !status.is_success() {
122 return Err(SdkError::Api { status, body });
123 }
124 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
125 }
126
127 pub async fn update_webhook(
134 &self,
135 id: &str,
136 params: &UpdateWebhookParams,
137 ) -> Result<Webhook, SdkError> {
138 let url = self
139 .config
140 .webhooks()
141 .base_url
142 .join(&format!("webhooks/{id}"))?;
143 let resp = self
144 .config
145 .http_client()
146 .patch(url)
147 .json(params)
148 .send()
149 .await
150 .map_err(SdkError::Http)?;
151 let status = resp.status();
152 let body = resp.text().await.map_err(SdkError::Http)?;
153 if !status.is_success() {
154 return Err(SdkError::Api { status, body });
155 }
156 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
157 }
158
159 pub async fn delete_webhook(&self, id: &str) -> Result<(), SdkError> {
161 let url = self
162 .config
163 .webhooks()
164 .base_url
165 .join(&format!("webhooks/{id}"))?;
166 let resp = self
167 .config
168 .http_client()
169 .delete(url)
170 .send()
171 .await
172 .map_err(SdkError::Http)?;
173 let status = resp.status();
174 if !status.is_success() {
175 let body = resp.text().await.map_err(SdkError::Http)?;
176 return Err(SdkError::Api { status, body });
177 }
178 Ok(())
179 }
180
181 pub async fn pause_webhook(&self, id: &str) -> Result<(), SdkError> {
183 let url = self
184 .config
185 .webhooks()
186 .base_url
187 .join(&format!("webhooks/{id}/pause"))?;
188 let resp = self
189 .config
190 .http_client()
191 .post(url)
192 .send()
193 .await
194 .map_err(SdkError::Http)?;
195 let status = resp.status();
196 if !status.is_success() {
197 let body = resp.text().await.map_err(SdkError::Http)?;
198 return Err(SdkError::Api { status, body });
199 }
200 Ok(())
201 }
202
203 pub async fn activate_webhook(
208 &self,
209 id: &str,
210 params: &ActivateWebhookParams,
211 ) -> Result<(), SdkError> {
212 let url = self
213 .config
214 .webhooks()
215 .base_url
216 .join(&format!("webhooks/{id}/activate"))?;
217 let resp = self
218 .config
219 .http_client()
220 .post(url)
221 .json(params)
222 .send()
223 .await
224 .map_err(SdkError::Http)?;
225 let status = resp.status();
226 if !status.is_success() {
227 let body = resp.text().await.map_err(SdkError::Http)?;
228 return Err(SdkError::Api { status, body });
229 }
230 Ok(())
231 }
232
233 pub async fn get_enabled_count(&self) -> Result<WebhookEnabledCountResponse, SdkError> {
236 let url = self
237 .config
238 .webhooks()
239 .base_url
240 .join("webhooks/enabled_count")?;
241 let resp = self
242 .config
243 .http_client()
244 .get(url)
245 .send()
246 .await
247 .map_err(SdkError::Http)?;
248 let status = resp.status();
249 let body = resp.text().await.map_err(SdkError::Http)?;
250 if !status.is_success() {
251 return Err(SdkError::Api { status, body });
252 }
253 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
254 }
255
256 pub async fn create_webhook_from_template(
264 &self,
265 params: &CreateWebhookFromTemplateParams,
266 ) -> Result<Webhook, SdkError> {
267 let template_id = params.template_args.tag().as_str();
268 let url = self
269 .config
270 .webhooks()
271 .base_url
272 .join(&format!("webhooks/template/{template_id}"))?;
273 let resp = self
274 .config
275 .http_client()
276 .post(url)
277 .json(params)
278 .send()
279 .await
280 .map_err(SdkError::Http)?;
281 let status = resp.status();
282 let body = resp.text().await.map_err(SdkError::Http)?;
283 if !status.is_success() {
284 return Err(SdkError::Api { status, body });
285 }
286 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
287 }
288
289 pub async fn update_webhook_template(
296 &self,
297 webhook_id: &str,
298 params: &UpdateWebhookTemplateParams,
299 ) -> Result<Webhook, SdkError> {
300 let template_id = params.template_args.tag().as_str();
301 let url = self
302 .config
303 .webhooks()
304 .base_url
305 .join(&format!("webhooks/{webhook_id}/template/{template_id}"))?;
306 let resp = self
307 .config
308 .http_client()
309 .patch(url)
310 .json(params)
311 .send()
312 .await
313 .map_err(SdkError::Http)?;
314 let status = resp.status();
315 let body = resp.text().await.map_err(SdkError::Http)?;
316 if !status.is_success() {
317 return Err(SdkError::Api { status, body });
318 }
319 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
320 }
321}
322
323#[cfg(test)]
326#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
327mod tests {
328 use super::*;
329 use crate::{QuicknodeSdk, SdkFullConfig, WebhooksConfig};
330 use wiremock::matchers::{method, path, path_regex};
331 use wiremock::{Mock, MockServer, ResponseTemplate};
332
333 fn make_sdk(base_url: String) -> QuicknodeSdk {
334 QuicknodeSdk::new(&SdkFullConfig {
335 api_key: "test-key".to_string(),
336 http: None,
337 admin: None,
338 streams: None,
339 webhooks: Some(WebhooksConfig {
340 base_url: Some(base_url),
341 }),
342 kvstore: None,
343 })
344 .unwrap()
345 }
346
347 fn webhook_response_json() -> serde_json::Value {
348 serde_json::json!({
349 "id": "wh-1234-5678",
350 "name": "test-webhook",
351 "status": "active",
352 "network": "ethereum-mainnet",
353 "created_at": "2026-03-19T12:00:00Z",
354 "updated_at": "2026-03-19T12:00:00Z"
355 })
356 }
357
358 #[tokio::test]
359 async fn list_webhooks_success() {
360 let server = MockServer::start().await;
361 let response = serde_json::json!({
362 "data": [webhook_response_json()],
363 "pageInfo": {
364 "limit": 20,
365 "offset": 0,
366 "total": 1,
367 }
368 });
369 Mock::given(method("GET"))
370 .and(path("/webhooks"))
371 .respond_with(ResponseTemplate::new(200).set_body_json(response))
372 .mount(&server)
373 .await;
374 let sdk = make_sdk(format!("{}/", server.uri()));
375 let resp = sdk
376 .webhooks
377 .list_webhooks(&GetWebhooksParams::default())
378 .await
379 .unwrap();
380 assert_eq!(resp.data.len(), 1);
381 assert_eq!(resp.data[0].id, "wh-1234-5678");
382 assert_eq!(resp.page_info.limit, 20);
383 assert_eq!(resp.page_info.offset, 0);
384 assert_eq!(resp.page_info.total, 1);
385 }
386
387 #[tokio::test]
388 async fn list_webhooks_api_error() {
389 let server = MockServer::start().await;
390 Mock::given(method("GET"))
391 .and(path("/webhooks"))
392 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
393 .mount(&server)
394 .await;
395 let sdk = make_sdk(format!("{}/", server.uri()));
396 let err = sdk
397 .webhooks
398 .list_webhooks(&GetWebhooksParams::default())
399 .await
400 .unwrap_err();
401 assert!(matches!(err, SdkError::Api { .. }));
402 }
403
404 #[tokio::test]
405 async fn list_webhooks_server_error() {
406 let server = MockServer::start().await;
407 Mock::given(method("GET"))
408 .and(path("/webhooks"))
409 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
410 .mount(&server)
411 .await;
412 let sdk = make_sdk(format!("{}/", server.uri()));
413 let err = sdk
414 .webhooks
415 .list_webhooks(&GetWebhooksParams::default())
416 .await
417 .unwrap_err();
418 assert!(matches!(err, SdkError::Api { .. }));
419 }
420
421 #[tokio::test]
422 async fn get_webhook_success() {
423 let server = MockServer::start().await;
424 Mock::given(method("GET"))
425 .and(path("/webhooks/test-id"))
426 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
427 .mount(&server)
428 .await;
429 let sdk = make_sdk(format!("{}/", server.uri()));
430 let resp = sdk.webhooks.get_webhook("test-id").await.unwrap();
431 assert_eq!(resp.id, "wh-1234-5678");
432 }
433
434 #[tokio::test]
435 async fn get_webhook_api_error() {
436 let server = MockServer::start().await;
437 Mock::given(method("GET"))
438 .and(path("/webhooks/test-id"))
439 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
440 .mount(&server)
441 .await;
442 let sdk = make_sdk(format!("{}/", server.uri()));
443 let err = sdk.webhooks.get_webhook("test-id").await.unwrap_err();
444 assert!(matches!(err, SdkError::Api { .. }));
445 }
446
447 #[tokio::test]
448 async fn update_webhook_success() {
449 let server = MockServer::start().await;
450 let mut updated = webhook_response_json();
451 updated["name"] = serde_json::json!("updated-name");
452 Mock::given(method("PATCH"))
453 .and(path("/webhooks/test-id"))
454 .respond_with(ResponseTemplate::new(200).set_body_json(updated))
455 .mount(&server)
456 .await;
457 let sdk = make_sdk(format!("{}/", server.uri()));
458 let params = UpdateWebhookParams {
459 name: Some("updated-name".to_string()),
460 ..Default::default()
461 };
462 let resp = sdk
463 .webhooks
464 .update_webhook("test-id", ¶ms)
465 .await
466 .unwrap();
467 assert_eq!(resp.name, "updated-name");
468 }
469
470 #[tokio::test]
471 async fn update_webhook_api_error() {
472 let server = MockServer::start().await;
473 Mock::given(method("PATCH"))
474 .and(path("/webhooks/test-id"))
475 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
476 .mount(&server)
477 .await;
478 let sdk = make_sdk(format!("{}/", server.uri()));
479 let params = UpdateWebhookParams::default();
480 let err = sdk
481 .webhooks
482 .update_webhook("test-id", ¶ms)
483 .await
484 .unwrap_err();
485 assert!(matches!(err, SdkError::Api { .. }));
486 }
487
488 #[tokio::test]
489 async fn delete_webhook_success() {
490 let server = MockServer::start().await;
491 Mock::given(method("DELETE"))
492 .and(path("/webhooks/test-id"))
493 .respond_with(ResponseTemplate::new(200))
494 .mount(&server)
495 .await;
496 let sdk = make_sdk(format!("{}/", server.uri()));
497 sdk.webhooks.delete_webhook("test-id").await.unwrap();
498 }
499
500 #[tokio::test]
501 async fn delete_webhook_api_error() {
502 let server = MockServer::start().await;
503 Mock::given(method("DELETE"))
504 .and(path("/webhooks/test-id"))
505 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
506 .mount(&server)
507 .await;
508 let sdk = make_sdk(format!("{}/", server.uri()));
509 let err = sdk.webhooks.delete_webhook("test-id").await.unwrap_err();
510 assert!(matches!(err, SdkError::Api { .. }));
511 }
512
513 #[tokio::test]
514 async fn delete_all_webhooks_success() {
515 let server = MockServer::start().await;
516 Mock::given(method("DELETE"))
517 .and(path("/webhooks"))
518 .respond_with(ResponseTemplate::new(204))
519 .mount(&server)
520 .await;
521 let sdk = make_sdk(format!("{}/", server.uri()));
522 sdk.webhooks.delete_all_webhooks().await.unwrap();
523 }
524
525 #[tokio::test]
526 async fn delete_all_webhooks_api_error() {
527 let server = MockServer::start().await;
528 Mock::given(method("DELETE"))
529 .and(path("/webhooks"))
530 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
531 .mount(&server)
532 .await;
533 let sdk = make_sdk(format!("{}/", server.uri()));
534 let err = sdk.webhooks.delete_all_webhooks().await.unwrap_err();
535 assert!(matches!(err, SdkError::Api { .. }));
536 }
537
538 #[tokio::test]
539 async fn pause_webhook_success() {
540 let server = MockServer::start().await;
541 Mock::given(method("POST"))
542 .and(path("/webhooks/test-id/pause"))
543 .respond_with(ResponseTemplate::new(201))
544 .mount(&server)
545 .await;
546 let sdk = make_sdk(format!("{}/", server.uri()));
547 sdk.webhooks.pause_webhook("test-id").await.unwrap();
548 }
549
550 #[tokio::test]
551 async fn pause_webhook_api_error() {
552 let server = MockServer::start().await;
553 Mock::given(method("POST"))
554 .and(path("/webhooks/test-id/pause"))
555 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
556 .mount(&server)
557 .await;
558 let sdk = make_sdk(format!("{}/", server.uri()));
559 let err = sdk.webhooks.pause_webhook("test-id").await.unwrap_err();
560 assert!(matches!(err, SdkError::Api { .. }));
561 }
562
563 #[tokio::test]
564 async fn activate_webhook_success() {
565 let server = MockServer::start().await;
566 Mock::given(method("POST"))
567 .and(path("/webhooks/test-id/activate"))
568 .respond_with(ResponseTemplate::new(201))
569 .mount(&server)
570 .await;
571 let sdk = make_sdk(format!("{}/", server.uri()));
572 let params = ActivateWebhookParams {
573 start_from: WebhookStartFrom::Latest,
574 };
575 sdk.webhooks
576 .activate_webhook("test-id", ¶ms)
577 .await
578 .unwrap();
579 }
580
581 #[tokio::test]
582 async fn activate_webhook_api_error() {
583 let server = MockServer::start().await;
584 Mock::given(method("POST"))
585 .and(path("/webhooks/test-id/activate"))
586 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
587 .mount(&server)
588 .await;
589 let sdk = make_sdk(format!("{}/", server.uri()));
590 let params = ActivateWebhookParams {
591 start_from: WebhookStartFrom::Latest,
592 };
593 let err = sdk
594 .webhooks
595 .activate_webhook("test-id", ¶ms)
596 .await
597 .unwrap_err();
598 assert!(matches!(err, SdkError::Api { .. }));
599 }
600
601 #[tokio::test]
602 async fn get_enabled_count_success() {
603 let server = MockServer::start().await;
604 Mock::given(method("GET"))
605 .and(path("/webhooks/enabled_count"))
606 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"total": 5})))
607 .mount(&server)
608 .await;
609 let sdk = make_sdk(format!("{}/", server.uri()));
610 let resp = sdk.webhooks.get_enabled_count().await.unwrap();
611 assert_eq!(resp.total, 5);
612 }
613
614 #[tokio::test]
615 async fn get_enabled_count_api_error() {
616 let server = MockServer::start().await;
617 Mock::given(method("GET"))
618 .and(path("/webhooks/enabled_count"))
619 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
620 .mount(&server)
621 .await;
622 let sdk = make_sdk(format!("{}/", server.uri()));
623 let err = sdk.webhooks.get_enabled_count().await.unwrap_err();
624 assert!(matches!(err, SdkError::Api { .. }));
625 }
626
627 #[tokio::test]
628 async fn create_webhook_from_template_success() {
629 let server = MockServer::start().await;
630 Mock::given(method("POST"))
631 .and(path_regex("/webhooks/template/evmWalletFilter"))
632 .respond_with(ResponseTemplate::new(201).set_body_json(webhook_response_json()))
633 .mount(&server)
634 .await;
635 let sdk = make_sdk(format!("{}/", server.uri()));
636 let template_args = TemplateArgs::EvmWalletFilter(EvmWalletFilterTemplate {
637 wallets: vec!["0xabc".to_string()],
638 });
639 let params = CreateWebhookFromTemplateParams {
640 name: "test-webhook".to_string(),
641 network: "ethereum-mainnet".to_string(),
642 notification_email: None,
643 destination_attributes: WebhookDestinationAttributes {
644 url: "https://example.com/hook".to_string(),
645 security_token: None,
646 compression: None,
647 },
648 template_args,
649 };
650 let resp = sdk
651 .webhooks
652 .create_webhook_from_template(¶ms)
653 .await
654 .unwrap();
655 assert_eq!(resp.id, "wh-1234-5678");
656 }
657
658 #[tokio::test]
659 async fn create_webhook_from_template_api_error() {
660 let server = MockServer::start().await;
661 Mock::given(method("POST"))
662 .and(path_regex("/webhooks/template/evmWalletFilter"))
663 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
664 .mount(&server)
665 .await;
666 let sdk = make_sdk(format!("{}/", server.uri()));
667 let template_args = TemplateArgs::EvmWalletFilter(EvmWalletFilterTemplate {
668 wallets: vec!["0xabc".to_string()],
669 });
670 let params = CreateWebhookFromTemplateParams {
671 name: "test-webhook".to_string(),
672 network: "ethereum-mainnet".to_string(),
673 notification_email: None,
674 destination_attributes: WebhookDestinationAttributes {
675 url: "https://example.com/hook".to_string(),
676 security_token: None,
677 compression: None,
678 },
679 template_args,
680 };
681 let err = sdk
682 .webhooks
683 .create_webhook_from_template(¶ms)
684 .await
685 .unwrap_err();
686 assert!(matches!(err, SdkError::Api { .. }));
687 }
688
689 #[tokio::test]
690 async fn update_webhook_template_success() {
691 let server = MockServer::start().await;
692 Mock::given(method("PATCH"))
693 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
694 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
695 .mount(&server)
696 .await;
697 let sdk = make_sdk(format!("{}/", server.uri()));
698 let template_args = TemplateArgs::EvmWalletFilter(EvmWalletFilterTemplate {
699 wallets: vec!["0xabc".to_string()],
700 });
701 let params = UpdateWebhookTemplateParams {
702 name: None,
703 notification_email: None,
704 destination_attributes: None,
705 template_args,
706 };
707 let resp = sdk
708 .webhooks
709 .update_webhook_template("test-id", ¶ms)
710 .await
711 .unwrap();
712 assert_eq!(resp.id, "wh-1234-5678");
713 }
714
715 #[tokio::test]
718 async fn update_webhook_template_wire_body_includes_name() {
719 use wiremock::matchers::body_partial_json;
720 let server = MockServer::start().await;
721 Mock::given(method("PATCH"))
722 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
723 .and(body_partial_json(serde_json::json!({"name": "new-name"})))
724 .respond_with(ResponseTemplate::new(200).set_body_json(webhook_response_json()))
725 .mount(&server)
726 .await;
727 let sdk = make_sdk(format!("{}/", server.uri()));
728 let template_args = TemplateArgs::EvmWalletFilter(EvmWalletFilterTemplate {
729 wallets: vec!["0xabc".to_string()],
730 });
731 let params = UpdateWebhookTemplateParams {
732 name: Some("new-name".to_string()),
733 notification_email: None,
734 destination_attributes: None,
735 template_args,
736 };
737 sdk.webhooks
738 .update_webhook_template("test-id", ¶ms)
739 .await
740 .unwrap();
741 }
742
743 #[tokio::test]
747 async fn create_webhook_from_template_wire_body_uses_camelcase_event_hashes() {
748 use wiremock::matchers::body_partial_json;
749 let server = MockServer::start().await;
750 Mock::given(method("POST"))
751 .and(path_regex("/webhooks/template/evmContractEvents"))
752 .and(body_partial_json(serde_json::json!({
753 "templateArgs": {
754 "eventHashes": ["0xabcd"],
755 }
756 })))
757 .respond_with(ResponseTemplate::new(201).set_body_json(webhook_response_json()))
758 .mount(&server)
759 .await;
760 let sdk = make_sdk(format!("{}/", server.uri()));
761 let template_args = TemplateArgs::EvmContractEvents(EvmContractEventsTemplate {
762 contracts: vec!["0xa0b8".to_string()],
763 event_hashes: Some(vec!["0xabcd".to_string()]),
764 });
765 let params = CreateWebhookFromTemplateParams {
766 name: "test-webhook".to_string(),
767 network: "ethereum-mainnet".to_string(),
768 notification_email: None,
769 destination_attributes: WebhookDestinationAttributes {
770 url: "https://example.com/hook".to_string(),
771 security_token: None,
772 compression: None,
773 },
774 template_args,
775 };
776 sdk.webhooks
777 .create_webhook_from_template(¶ms)
778 .await
779 .unwrap();
780 }
781
782 #[tokio::test]
783 async fn update_webhook_template_api_error() {
784 let server = MockServer::start().await;
785 Mock::given(method("PATCH"))
786 .and(path_regex("/webhooks/test-id/template/evmWalletFilter"))
787 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
788 .mount(&server)
789 .await;
790 let sdk = make_sdk(format!("{}/", server.uri()));
791 let template_args = TemplateArgs::EvmWalletFilter(EvmWalletFilterTemplate {
792 wallets: vec!["0xabc".to_string()],
793 });
794 let params = UpdateWebhookTemplateParams {
795 name: None,
796 notification_email: None,
797 destination_attributes: None,
798 template_args,
799 };
800 let err = sdk
801 .webhooks
802 .update_webhook_template("test-id", ¶ms)
803 .await
804 .unwrap_err();
805 assert!(matches!(err, SdkError::Api { .. }));
806 }
807}