1#![doc(html_root_url = "https://docs.rs/nestrs-webhooks/1.7.1")]
11
12mod module;
13mod options;
14mod sign;
15mod verify;
16
17pub use module::WebhooksModule;
18pub use options::WebhooksOptions;
19pub use sign::{sign_standard_webhook, SignedWebhook};
20pub use verify::{verify_standard_webhook, WebhookVerifyError};
21
22pub use nestrs_core::{DynamicModule, Injectable, ProviderRegistry};
23use nestrs_outbox::OutboxRelay;
24use serde_json::json;
25use std::sync::Arc;
26
27pub struct WebhooksService {
28 secret: String,
29 outbox: Option<Arc<OutboxRelay>>,
30}
31
32impl WebhooksService {
33 pub fn new(secret: impl Into<String>, outbox: Option<Arc<OutboxRelay>>) -> Self {
34 Self {
35 secret: secret.into(),
36 outbox,
37 }
38 }
39
40 pub fn sign(&self, msg_id: &str, timestamp: i64, body: &[u8]) -> SignedWebhook {
42 sign_standard_webhook(&self.secret, msg_id, timestamp, body)
43 }
44
45 pub async fn dispatch(
47 &self,
48 topic: &str,
49 url: &str,
50 body: serde_json::Value,
51 ) -> Result<(), String> {
52 let Some(outbox) = &self.outbox else {
53 return Err("webhooks: outbox not configured".into());
54 };
55 outbox
56 .add(
57 topic,
58 json!({
59 "url": url,
60 "body": body,
61 }),
62 )
63 .await;
64 Ok(())
65 }
66
67 pub fn verify(
68 &self,
69 msg_id: &str,
70 timestamp: &str,
71 signature: &str,
72 body: &[u8],
73 ) -> Result<(), WebhookVerifyError> {
74 verify_standard_webhook(&self.secret, msg_id, timestamp, signature, body)
75 }
76}
77
78impl Injectable for WebhooksService {
79 fn construct(_registry: &nestrs_core::ProviderRegistry) -> Arc<Self> {
80 panic!("WebhooksService must be provided by WebhooksModule::register(...)");
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn sign_and_verify_roundtrip() {
90 let secret = "whsec_test";
91 let body = br#"{"hello":"world"}"#;
92 let msg_id = "msg_1";
93 let ts = 1_700_000_000_i64;
94 let signed = sign_standard_webhook(secret, msg_id, ts, body);
95 verify_standard_webhook(
96 secret,
97 msg_id,
98 &ts.to_string(),
99 &signed.signature_header,
100 body,
101 )
102 .expect("verify");
103 }
104}