Sample code for 30+ languages & platforms
Rust Requires Chilkat v11.5.0+

Box: Verify a Captured V2 Webhook Signature

See more Box Examples

Verify raw payload bytes and captured headers using the configured primary and secondary signing keys. The example rejects stale/future timestamps and compares randomized HMACs of the signatures to avoid exposing a signature-prefix timing comparison. It does not host an HTTP listener. The receiver must persist/deduplicate the verified event and acknowledge promptly.

See the Box API guide for setup and the complete workflow.

Chilkat Rust Downloads

Rust

// Requires Chilkat 11.5.0 or later and a previously unlocked Chilkat bundle.
// See Global Unlock Sample.
// Guide: Box API Guide.

let _ = false;

// Save the exact HTTP body bytes to this file, without JSON reserialization.
let raw_body = chilkat::BinData::new();
if raw_body.load_file("qa_data/box_webhook_body.bin").is_err() {
    println!("{}", raw_body.last_error_text());
    return;
}

// Read these fields case-insensitively from the incoming request headers.
// For this offline example, enter the captured header values.
let timestamp = "BOX_DELIVERY_TIMESTAMP".to_string();
let primary_signature = "BOX_SIGNATURE_PRIMARY".to_string();
let secondary_signature = "BOX_SIGNATURE_SECONDARY".to_string();
let signature_version = "1".to_string();
let signature_algorithm = "HmacSHA256".to_string();
// Retrieve configured keys from protected application configuration.
let primary_key = "BOX_PRIMARY_SIGNATURE_KEY".to_string();
let secondary_key = "BOX_SECONDARY_SIGNATURE_KEY".to_string();
if (signature_version != "1") || (signature_algorithm != "HmacSHA256") {
    println!("Unsupported signature version or algorithm.");
    return;
}

let delivered = chilkat::DateTime::new();
if delivered.set_from_timestamp(&timestamp).is_err() {
    println!("{}", delivered.last_error_text());
    return;
}

let now = chilkat::DateTime::new();
if now.set_from_current_system_time().is_err() {
    println!("{}", now.last_error_text());
    return;
}

let age = (now.get_as_unix_time(false)) as i32 - (delivered.get_as_unix_time(false)) as i32;
// Box recommends rejecting deliveries older than ten minutes.
// Allow at most 60 seconds of future clock skew; keep the server clock synchronized.
if (age > 600) || (age < -60) {
    println!("Delivery timestamp is outside the accepted window.");
    return;
}

let signed_bytes = chilkat::BinData::new();
if signed_bytes.append_bd(&raw_body).is_err() {
    println!("{}", signed_bytes.last_error_text());
    return;
}

if signed_bytes.append_string(&timestamp, "utf-8").is_err() {
    println!("{}", signed_bytes.last_error_text());
    return;
}

let mac = chilkat::Crypt2::new();
mac.set_mac_algorithm("hmac");
mac.set_hash_algorithm("sha256");
mac.set_encoding_mode("base64");
if mac.set_mac_key_string(&primary_key).is_err() {
    println!("{}", mac.last_error_text());
    return;
}

let Ok(expected_primary) = mac.mac_bd_enc(&signed_bytes) else {
    println!("{}", mac.last_error_text());
    return;
};

if mac.set_mac_key_string(&secondary_key).is_err() {
    println!("{}", mac.last_error_text());
    return;
}

let Ok(expected_secondary) = mac.mac_bd_enc(&signed_bytes) else {
    println!("{}", mac.last_error_text());
    return;
};

// Blind both sides with a fresh secret random key before equality comparison.
// Never compare the original HMAC strings using a prefix-short-circuit comparison.
// A host-language receiver can instead use its standard constant-time byte comparison.
let random = chilkat::Prng::new();
let Ok(comparison_key) = random.gen_random(32, "base64") else {
    println!("{}", random.last_error_text());
    return;
};

if mac.set_mac_key_encoded(&comparison_key, "base64").is_err() {
    println!("{}", mac.last_error_text());
    return;
}

let Ok(p_expected) = mac.mac_string_enc(&expected_primary) else {
    println!("{}", mac.last_error_text());
    return;
};

let Ok(p_received) = mac.mac_string_enc(&primary_signature) else {
    println!("{}", mac.last_error_text());
    return;
};

let Ok(s_expected) = mac.mac_string_enc(&expected_secondary) else {
    println!("{}", mac.last_error_text());
    return;
};

let Ok(s_received) = mac.mac_string_enc(&secondary_signature) else {
    println!("{}", mac.last_error_text());
    return;
};

let valid_primary = (primary_key != "") && ((primary_signature != "") && (p_expected == p_received));
let valid_secondary = (secondary_key != "") && ((secondary_signature != "") && (s_expected == s_received));
if (!valid_primary) && (!valid_secondary) {
    println!("Signature verification failed.");
    return;
}

// Parse JSON only after signature verification.
let webhook_event = chilkat::JsonObject::new();
if webhook_event.load(&raw_body.get_string("utf-8").unwrap_or_default()).is_err() {
    println!("{}", webhook_event.last_error_text());
    return;
}

println!("Verified event ID: {}", webhook_event.string_of("id").unwrap_or_default());
println!("Trigger: {}", webhook_event.string_of("trigger").unwrap_or_default());
// Deduplicate using event.id, not BOX-DELIVERY-ID, which changes on retries.
// Verify the target belongs to your configured webhook before dispatching work.