Sample code for 30+ languages & platforms
PowerBuilder 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 PowerBuilder Downloads

PowerBuilder
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