PowerBuilder Requires Chilkat v11.5.0+
PowerBuilder
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 PowerBuilder Downloads
integer li_rc
long li_Success
oleobject loo_RawBody
string ls_Timestamp
string ls_PrimarySignature
string ls_SecondarySignature
string ls_SignatureVersion
string ls_SignatureAlgorithm
string ls_PrimaryKey
string ls_SecondaryKey
oleobject loo_Delivered
oleobject loo_Now
long li_Age
oleobject loo_SignedBytes
oleobject loo_Mac
string ls_ExpectedPrimary
string ls_ExpectedSecondary
oleobject loo_Random
string ls_ComparisonKey
string ls_PExpected
string ls_PReceived
string ls_SExpected
string ls_SReceived
long li_ValidPrimary
long li_ValidSecondary
oleobject loo_WebhookEvent
li_Success = 0
// Requires Chilkat 11.5.0 or later and a previously unlocked Chilkat bundle.
// See Global Unlock Sample.
// Guide: Box API Guide.
li_Success = 0
// Save the exact HTTP body bytes to this file, without JSON reserialization.
loo_RawBody = create oleobject
li_rc = loo_RawBody.ConnectToNewObject("Chilkat.BinData")
if li_rc < 0 then
destroy loo_RawBody
MessageBox("Error","Connecting to COM object failed")
return
end if
li_Success = loo_RawBody.LoadFile("qa_data/box_webhook_body.bin")
if li_Success <> 1 then
Write-Debug loo_RawBody.LastErrorText
destroy loo_RawBody
return
end if
// Read these fields case-insensitively from the incoming request headers.
// For this offline example, enter the captured header values.
ls_Timestamp = "BOX_DELIVERY_TIMESTAMP"
ls_PrimarySignature = "BOX_SIGNATURE_PRIMARY"
ls_SecondarySignature = "BOX_SIGNATURE_SECONDARY"
ls_SignatureVersion = "1"
ls_SignatureAlgorithm = "HmacSHA256"
// Retrieve configured keys from protected application configuration.
ls_PrimaryKey = "BOX_PRIMARY_SIGNATURE_KEY"
ls_SecondaryKey = "BOX_SECONDARY_SIGNATURE_KEY"
if (ls_SignatureVersion <> "1") OR (ls_SignatureAlgorithm <> "HmacSHA256") then
Write-Debug "Unsupported signature version or algorithm."
destroy loo_RawBody
return
end if
loo_Delivered = create oleobject
li_rc = loo_Delivered.ConnectToNewObject("Chilkat.CkDateTime")
li_Success = loo_Delivered.SetFromTimestamp(ls_Timestamp)
if li_Success <> 1 then
Write-Debug loo_Delivered.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
return
end if
loo_Now = create oleobject
li_rc = loo_Now.ConnectToNewObject("Chilkat.CkDateTime")
li_Success = loo_Now.SetFromCurrentSystemTime()
if li_Success <> 1 then
Write-Debug loo_Now.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
return
end if
li_Age = Long(loo_Now.GetAsUnixTime(0)) - Long(loo_Delivered.GetAsUnixTime(0))
// Box recommends rejecting deliveries older than ten minutes.
// Allow at most 60 seconds of future clock skew; keep the server clock synchronized.
if (li_Age > 600) OR (li_Age < -60) then
Write-Debug "Delivery timestamp is outside the accepted window."
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
return
end if
loo_SignedBytes = create oleobject
li_rc = loo_SignedBytes.ConnectToNewObject("Chilkat.BinData")
li_Success = loo_SignedBytes.AppendBd(loo_RawBody)
if li_Success <> 1 then
Write-Debug loo_SignedBytes.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
return
end if
li_Success = loo_SignedBytes.AppendString(ls_Timestamp,"utf-8")
if li_Success <> 1 then
Write-Debug loo_SignedBytes.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
return
end if
loo_Mac = create oleobject
li_rc = loo_Mac.ConnectToNewObject("Chilkat.Crypt2")
loo_Mac.MacAlgorithm = "hmac"
loo_Mac.HashAlgorithm = "sha256"
loo_Mac.EncodingMode = "base64"
li_Success = loo_Mac.SetMacKeyString(ls_PrimaryKey)
if li_Success <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
return
end if
ls_ExpectedPrimary = loo_Mac.MacBdENC(loo_SignedBytes)
if loo_Mac.LastMethodSuccess <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
return
end if
li_Success = loo_Mac.SetMacKeyString(ls_SecondaryKey)
if li_Success <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
return
end if
ls_ExpectedSecondary = loo_Mac.MacBdENC(loo_SignedBytes)
if loo_Mac.LastMethodSuccess <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
return
end if
// 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.
loo_Random = create oleobject
li_rc = loo_Random.ConnectToNewObject("Chilkat.Prng")
ls_ComparisonKey = loo_Random.GenRandom(32,"base64")
if loo_Random.LastMethodSuccess <> 1 then
Write-Debug loo_Random.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
li_Success = loo_Mac.SetMacKeyEncoded(ls_ComparisonKey,"base64")
if li_Success <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
ls_PExpected = loo_Mac.MacStringENC(ls_ExpectedPrimary)
if loo_Mac.LastMethodSuccess <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
ls_PReceived = loo_Mac.MacStringENC(ls_PrimarySignature)
if loo_Mac.LastMethodSuccess <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
ls_SExpected = loo_Mac.MacStringENC(ls_ExpectedSecondary)
if loo_Mac.LastMethodSuccess <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
ls_SReceived = loo_Mac.MacStringENC(ls_SecondarySignature)
if loo_Mac.LastMethodSuccess <> 1 then
Write-Debug loo_Mac.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
li_ValidPrimary = (ls_PrimaryKey <> "") AND (ls_PrimarySignature <> "") AND (ls_PExpected = ls_PReceived)
li_ValidSecondary = (ls_SecondaryKey <> "") AND (ls_SecondarySignature <> "") AND (ls_SExpected = ls_SReceived)
if (li_ValidPrimary = 0) AND (li_ValidSecondary = 0) then
Write-Debug "Signature verification failed."
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
return
end if
// Parse JSON only after signature verification.
loo_WebhookEvent = create oleobject
li_rc = loo_WebhookEvent.ConnectToNewObject("Chilkat.JsonObject")
li_Success = loo_WebhookEvent.Load(loo_RawBody.GetString("utf-8"))
if li_Success <> 1 then
Write-Debug loo_WebhookEvent.LastErrorText
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
destroy loo_WebhookEvent
return
end if
Write-Debug "Verified event ID: " + loo_WebhookEvent.StringOf("id")
Write-Debug "Trigger: " + loo_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.
destroy loo_RawBody
destroy loo_Delivered
destroy loo_Now
destroy loo_SignedBytes
destroy loo_Mac
destroy loo_Random
destroy loo_WebhookEvent