Skip to main content

nestrs_webhooks/
verify.rs

1use crate::sign::sign_standard_webhook;
2use subtle::ConstantTimeEq;
3
4#[derive(Debug, Clone)]
5pub enum WebhookVerifyError {
6    BadTimestamp,
7    BadSignature,
8}
9
10impl std::fmt::Display for WebhookVerifyError {
11    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12        match self {
13            Self::BadTimestamp => write!(f, "invalid webhook timestamp"),
14            Self::BadSignature => write!(f, "invalid webhook signature"),
15        }
16    }
17}
18
19impl std::error::Error for WebhookVerifyError {}
20
21pub fn verify_standard_webhook(
22    secret: &str,
23    msg_id: &str,
24    timestamp: &str,
25    signature_header: &str,
26    body: &[u8],
27) -> Result<(), WebhookVerifyError> {
28    let ts: i64 = timestamp
29        .parse()
30        .map_err(|_| WebhookVerifyError::BadTimestamp)?;
31    let expected = sign_standard_webhook(secret, msg_id, ts, body);
32    // Header may contain multiple `v1,<sig>` entries separated by spaces.
33    let matched = signature_header.split_whitespace().any(|part| {
34        expected
35            .signature_header
36            .as_bytes()
37            .ct_eq(part.as_bytes())
38            .into()
39    });
40    if matched {
41        Ok(())
42    } else {
43        Err(WebhookVerifyError::BadSignature)
44    }
45}