Xbase++ Requires Chilkat v11.5.0+
Xbase++
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 Xbase++ Downloads
LOCAL nSuccess
LOCAL oRawBody
LOCAL cTimestamp
LOCAL cPrimarySignature
LOCAL cSecondarySignature
LOCAL cSignatureVersion
LOCAL cSignatureAlgorithm
LOCAL cPrimaryKey
LOCAL cSecondaryKey
LOCAL oDelivered
LOCAL oNow
LOCAL nAge
LOCAL oSignedBytes
LOCAL oMac
LOCAL cExpectedPrimary
LOCAL cExpectedSecondary
LOCAL oRandom
LOCAL cComparisonKey
LOCAL cPExpected
LOCAL cPReceived
LOCAL cSExpected
LOCAL cSReceived
LOCAL nValidPrimary
LOCAL nValidSecondary
LOCAL oWebhookEvent
nSuccess := 0
// Requires Chilkat 11.5.0 or later and a previously unlocked Chilkat bundle.
// See Global Unlock Sample.
// Guide: Box API Guide.
nSuccess := 0
// Save the exact HTTP body bytes to this file, without JSON reserialization.
oRawBody := CreateObject("Chilkat.BinData")
nSuccess := oRawBody:LoadFile("qa_data/box_webhook_body.bin")
IF (nSuccess != 1)
? oRawBody:LastErrorText
oRawBody:destroy()
RETURN
ENDIF
// Read these fields case-insensitively from the incoming request headers.
// For this offline example, enter the captured header values.
cTimestamp := "BOX_DELIVERY_TIMESTAMP"
cPrimarySignature := "BOX_SIGNATURE_PRIMARY"
cSecondarySignature := "BOX_SIGNATURE_SECONDARY"
cSignatureVersion := "1"
cSignatureAlgorithm := "HmacSHA256"
// Retrieve configured keys from protected application configuration.
cPrimaryKey := "BOX_PRIMARY_SIGNATURE_KEY"
cSecondaryKey := "BOX_SECONDARY_SIGNATURE_KEY"
IF ((cSignatureVersion != "1") .OR. (cSignatureAlgorithm != "HmacSHA256"))
? "Unsupported signature version or algorithm."
oRawBody:destroy()
RETURN
ENDIF
oDelivered := CreateObject("Chilkat.CkDateTime")
nSuccess := oDelivered:SetFromTimestamp(cTimestamp)
IF (nSuccess != 1)
? oDelivered:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
RETURN
ENDIF
oNow := CreateObject("Chilkat.CkDateTime")
nSuccess := oNow:SetFromCurrentSystemTime()
IF (nSuccess != 1)
? oNow:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
RETURN
ENDIF
nAge := Val(oNow:GetAsUnixTime(0)) - Val(oDelivered: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 ((nAge > 600) .OR. (nAge < -60))
? "Delivery timestamp is outside the accepted window."
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
RETURN
ENDIF
oSignedBytes := CreateObject("Chilkat.BinData")
nSuccess := oSignedBytes:AppendBd(oRawBody)
IF (nSuccess != 1)
? oSignedBytes:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
RETURN
ENDIF
nSuccess := oSignedBytes:AppendString(cTimestamp, "utf-8")
IF (nSuccess != 1)
? oSignedBytes:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
RETURN
ENDIF
oMac := CreateObject("Chilkat.Crypt2")
oMac:MacAlgorithm := "hmac"
oMac:HashAlgorithm := "sha256"
oMac:EncodingMode := "base64"
nSuccess := oMac:SetMacKeyString(cPrimaryKey)
IF (nSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
RETURN
ENDIF
cExpectedPrimary := oMac:MacBdENC(oSignedBytes)
IF (oMac:LastMethodSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
RETURN
ENDIF
nSuccess := oMac:SetMacKeyString(cSecondaryKey)
IF (nSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
RETURN
ENDIF
cExpectedSecondary := oMac:MacBdENC(oSignedBytes)
IF (oMac:LastMethodSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
RETURN
ENDIF
// 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.
oRandom := CreateObject("Chilkat.Prng")
cComparisonKey := oRandom:GenRandom(32, "base64")
IF (oRandom:LastMethodSuccess != 1)
? oRandom:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
nSuccess := oMac:SetMacKeyEncoded(cComparisonKey, "base64")
IF (nSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
cPExpected := oMac:MacStringENC(cExpectedPrimary)
IF (oMac:LastMethodSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
cPReceived := oMac:MacStringENC(cPrimarySignature)
IF (oMac:LastMethodSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
cSExpected := oMac:MacStringENC(cExpectedSecondary)
IF (oMac:LastMethodSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
cSReceived := oMac:MacStringENC(cSecondarySignature)
IF (oMac:LastMethodSuccess != 1)
? oMac:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
nValidPrimary := (cPrimaryKey != "") .AND. (cPrimarySignature != "") .AND. (cPExpected == cPReceived)
nValidSecondary := (cSecondaryKey != "") .AND. (cSecondarySignature != "") .AND. (cSExpected == cSReceived)
IF ((nValidPrimary == 0) .AND. (nValidSecondary == 0))
? "Signature verification failed."
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
RETURN
ENDIF
// Parse JSON only after signature verification.
oWebhookEvent := CreateObject("Chilkat.JsonObject")
nSuccess := oWebhookEvent:Load(oRawBody:GetString("utf-8"))
IF (nSuccess != 1)
? oWebhookEvent:LastErrorText
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
oWebhookEvent:destroy()
RETURN
ENDIF
? "Verified event ID: " + oWebhookEvent:StringOf("id")
? "Trigger: " + oWebhookEvent: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.
oRawBody:destroy()
oDelivered:destroy()
oNow:destroy()
oSignedBytes:destroy()
oMac:destroy()
oRandom:destroy()
oWebhookEvent:destroy()