vylth-flow 0.3.2

Official Rust SDK for Vylth Flow — self-custody crypto payment processing
Documentation
use hmac::{Hmac, Mac};
use sha2::Sha256;

use crate::error::FlowError;
use crate::types::WebhookEvent;

type HmacSha256 = Hmac<Sha256>;

/// Webhook signature verifier.
pub struct WebhookVerifier {
    secret: String,
}

impl WebhookVerifier {
    pub fn new(secret: &str) -> Self {
        Self {
            secret: secret.to_string(),
        }
    }

    /// Verify a webhook payload and return the parsed event.
    pub fn verify(&self, payload: &[u8], signature: &str) -> Result<WebhookEvent, FlowError> {
        if self.secret.is_empty() {
            return Err(FlowError::InvalidSignature(
                "webhook secret not configured".into(),
            ));
        }

        let mut mac = HmacSha256::new_from_slice(self.secret.as_bytes())
            .map_err(|e| FlowError::Other(e.to_string()))?;
        mac.update(payload);
        let expected = hex::encode(mac.finalize().into_bytes());

        if !constant_time_eq(expected.as_bytes(), signature.as_bytes()) {
            return Err(FlowError::InvalidSignature(
                "signature mismatch".into(),
            ));
        }

        let event: WebhookEvent = serde_json::from_slice(payload)
            .map_err(|e| FlowError::Other(format!("parse webhook event: {}", e)))?;
        Ok(event)
    }

    /// Check if a webhook signature is valid without parsing.
    pub fn is_valid(&self, payload: &[u8], signature: &str) -> bool {
        if self.secret.is_empty() {
            return false;
        }
        let Ok(mut mac) = HmacSha256::new_from_slice(self.secret.as_bytes()) else {
            return false;
        };
        mac.update(payload);
        let expected = hex::encode(mac.finalize().into_bytes());
        constant_time_eq(expected.as_bytes(), signature.as_bytes())
    }
}

fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
    if a.len() != b.len() {
        return false;
    }
    a.iter()
        .zip(b.iter())
        .fold(0u8, |acc, (x, y)| acc | (x ^ y))
        == 0
}