PowerShell Requires Chilkat v11.5.0+
PowerShell
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 PowerShell Downloads
Add-Type -Path "C:\chilkat\ChilkatDotNet47-x64\ChilkatDotNet47.dll"
$success = $false
# Requires Chilkat 11.5.0 or later and a previously unlocked Chilkat bundle.
# See Global Unlock Sample.
# Guide: Box API Guide.
$success = $false
# Save the exact HTTP body bytes to this file, without JSON reserialization.
$rawBody = New-Object Chilkat.BinData
$success = $rawBody.LoadFile("qa_data/box_webhook_body.bin")
if ($success -ne $true) {
$($rawBody.LastErrorText)
exit
}
# Read these fields case-insensitively from the incoming request headers.
# For this offline example, enter the captured header values.
$timestamp = "BOX_DELIVERY_TIMESTAMP"
$primarySignature = "BOX_SIGNATURE_PRIMARY"
$secondarySignature = "BOX_SIGNATURE_SECONDARY"
$signatureVersion = "1"
$signatureAlgorithm = "HmacSHA256"
# Retrieve configured keys from protected application configuration.
$primaryKey = "BOX_PRIMARY_SIGNATURE_KEY"
$secondaryKey = "BOX_SECONDARY_SIGNATURE_KEY"
if (($signatureVersion -ne "1") -or ($signatureAlgorithm -ne "HmacSHA256")) {
$("Unsupported signature version or algorithm.")
exit
}
$delivered = New-Object Chilkat.CkDateTime
$success = $delivered.SetFromTimestamp($timestamp)
if ($success -ne $true) {
$($delivered.LastErrorText)
exit
}
$now = New-Object Chilkat.CkDateTime
$success = $now.SetFromCurrentSystemTime()
if ($success -ne $true) {
$($now.LastErrorText)
exit
}
$age = $now.GetAsUnixTime($false) - $delivered.GetAsUnixTime($false)
# Box recommends rejecting deliveries older than ten minutes.
# Allow at most 60 seconds of future clock skew; keep the server clock synchronized.
if (($age -gt 600) -or ($age -lt -60)) {
$("Delivery timestamp is outside the accepted window.")
exit
}
$signedBytes = New-Object Chilkat.BinData
$success = $signedBytes.AppendBd($rawBody)
if ($success -ne $true) {
$($signedBytes.LastErrorText)
exit
}
$success = $signedBytes.AppendString($timestamp,"utf-8")
if ($success -ne $true) {
$($signedBytes.LastErrorText)
exit
}
$mac = New-Object Chilkat.Crypt2
$mac.MacAlgorithm = "hmac"
$mac.HashAlgorithm = "sha256"
$mac.EncodingMode = "base64"
$success = $mac.SetMacKeyString($primaryKey)
if ($success -ne $true) {
$($mac.LastErrorText)
exit
}
$expectedPrimary = $mac.MacBdENC($signedBytes)
if ($mac.LastMethodSuccess -ne $true) {
$($mac.LastErrorText)
exit
}
$success = $mac.SetMacKeyString($secondaryKey)
if ($success -ne $true) {
$($mac.LastErrorText)
exit
}
$expectedSecondary = $mac.MacBdENC($signedBytes)
if ($mac.LastMethodSuccess -ne $true) {
$($mac.LastErrorText)
exit
}
# 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.
$random = New-Object Chilkat.Prng
$comparisonKey = $random.GenRandom(32,"base64")
if ($random.LastMethodSuccess -ne $true) {
$($random.LastErrorText)
exit
}
$success = $mac.SetMacKeyEncoded($comparisonKey,"base64")
if ($success -ne $true) {
$($mac.LastErrorText)
exit
}
$pExpected = $mac.MacStringENC($expectedPrimary)
if ($mac.LastMethodSuccess -ne $true) {
$($mac.LastErrorText)
exit
}
$pReceived = $mac.MacStringENC($primarySignature)
if ($mac.LastMethodSuccess -ne $true) {
$($mac.LastErrorText)
exit
}
$sExpected = $mac.MacStringENC($expectedSecondary)
if ($mac.LastMethodSuccess -ne $true) {
$($mac.LastErrorText)
exit
}
$sReceived = $mac.MacStringENC($secondarySignature)
if ($mac.LastMethodSuccess -ne $true) {
$($mac.LastErrorText)
exit
}
$validPrimary = ($primaryKey -ne "") -and ($primarySignature -ne "") -and ($pExpected -eq $pReceived)
$validSecondary = ($secondaryKey -ne "") -and ($secondarySignature -ne "") -and ($sExpected -eq $sReceived)
if (($validPrimary -eq $false) -and ($validSecondary -eq $false)) {
$("Signature verification failed.")
exit
}
# Parse JSON only after signature verification.
$webhookEvent = New-Object Chilkat.JsonObject
$success = $webhookEvent.Load($rawBody.GetString("utf-8"))
if ($success -ne $true) {
$($webhookEvent.LastErrorText)
exit
}
$("Verified event ID: " + $webhookEvent.StringOf("id"))
$("Trigger: " + $webhookEvent.StringOf("trigger"))
# Deduplicate using event.id, not BOX-DELIVERY-ID, which changes on retries.
# Verify the target belongs to your configured webhook before dispatching work.