#![allow(clippy::print_stdout)]
use blooio::webhook::{
DEFAULT_TOLERANCE_SECS, MessageEventKind, SignatureHeader, WebhookEvent, peek, verify_preparsed,
};
use hmac::{Hmac, KeyInit, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let secret = b"whsec_example_secret";
let body = br#"{"event":"message.received","protocol":"sms","message_id":"m_123","internal_id":"+15555550100","text":"Hi there!","sender":"+15555550123"}"#;
let timestamp = 1_700_000_000;
let signature_header = sign(secret, timestamp, body)?;
let signature = SignatureHeader::parse(&signature_header)?;
signature.check_tolerance(timestamp, DEFAULT_TOLERANCE_SECS)?;
let peek = peek(body)?;
println!("routing internal_id: {:?}", peek.internal_id);
verify_preparsed(secret, &signature, body)?;
println!("signature OK");
let event = WebhookEvent::parse(body)?;
match event.kind() {
Some(MessageEventKind::Received) => {
println!(
"inbound message from {:?}: {:?}",
event.payload.sender, event.payload.text
);
}
Some(MessageEventKind::Delivered | MessageEventKind::Read) => {
println!("status update for {:?}", event.payload.message_id);
}
Some(other) => println!("other event: {other:?}"),
None => println!("payload carried no event field"),
}
let sms = event.try_into_received_sms()?;
println!("received SMS {} from {}", sms.message_id, sms.sender);
let tampered = br#"{"event":"message.received","text":"gotcha"}"#;
match verify_preparsed(secret, &signature, tampered) {
Ok(()) => println!("unexpected: tampered body verified"),
Err(e) => println!("tampered body rejected: {e}"),
}
Ok(())
}
fn sign(secret: &[u8], timestamp: i64, body: &[u8]) -> Result<String, hmac::digest::InvalidLength> {
let mut mac = HmacSha256::new_from_slice(secret)?;
mac.update(timestamp.to_string().as_bytes());
mac.update(b".");
mac.update(body);
Ok(format!(
"t={timestamp},v1={}",
hex::encode(mac.finalize().into_bytes())
))
}