pub fn verify_standard(
secret: &str,
headers: &HeaderMap,
body: &[u8],
tolerance: i64,
now: i64,
) -> Result<String>Expand description
Checks a Standard Webhooks delivery
(Svix, Resend, and other providers): webhook-signature holds one or
more space-separated v1,<base64> signatures of {id}.{timestamp}.{body}
keyed with the base64 part of the whsec_... secret, and the
webhook-timestamp must be within tolerance seconds of now (replays
of an old delivery are refused). Returns the webhook-id, the event id
to give once.
§Errors
Error::Unauthorized when a header is missing, the timestamp is out
of tolerance, or no signature matches; Error::Internal when the
secret is not whsec_ followed by base64.
§Examples
use axum::http::HeaderMap;
use base64::Engine as _;
use ocre::webhooks::{sign, verify_standard};
let key = b"0123456789abcdef";
let secret = format!("whsec_{}", base64::engine::general_purpose::STANDARD.encode(key));
let signature = sign(key, b"msg_1.1700000000.{}");
let bytes: Vec<u8> = (0..32).map(|i| u8::from_str_radix(&signature[i * 2..i * 2 + 2], 16).unwrap()).collect();
let mut headers = HeaderMap::new();
headers.insert("webhook-id", "msg_1".parse().unwrap());
headers.insert("webhook-timestamp", "1700000000".parse().unwrap());
let value = format!("v1,{}", base64::engine::general_purpose::STANDARD.encode(bytes));
headers.insert("webhook-signature", value.parse().unwrap());
assert_eq!(verify_standard(&secret, &headers, b"{}", 300, 1_700_000_100).unwrap(), "msg_1");
assert!(verify_standard(&secret, &headers, b"{}", 300, 1_700_001_000).is_err(), "too old");