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

Visual FoxPro
LOCAL lnSuccess
LOCAL loRawBody
LOCAL lcTimestamp
LOCAL lcPrimarySignature
LOCAL lcSecondarySignature
LOCAL lcSignatureVersion
LOCAL lcSignatureAlgorithm
LOCAL lcPrimaryKey
LOCAL lcSecondaryKey
LOCAL loDelivered
LOCAL loNow
LOCAL lnAge
LOCAL loSignedBytes
LOCAL loMac
LOCAL lcExpectedPrimary
LOCAL lcExpectedSecondary
LOCAL loRandom
LOCAL lcComparisonKey
LOCAL lcPExpected
LOCAL lcPReceived
LOCAL lcSExpected
LOCAL lcSReceived
LOCAL lnValidPrimary
LOCAL lnValidSecondary
LOCAL loWebhookEvent

lnSuccess = 0

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

lnSuccess = 0

*  Save the exact HTTP body bytes to this file, without JSON reserialization.
loRawBody = CreateObject('Chilkat.BinData')
lnSuccess = loRawBody.LoadFile("qa_data/box_webhook_body.bin")
IF (lnSuccess <> 1) THEN
    ? loRawBody.LastErrorText
    RELEASE loRawBody
    CANCEL
ENDIF

*  Read these fields case-insensitively from the incoming request headers.
*  For this offline example, enter the captured header values.
lcTimestamp = "BOX_DELIVERY_TIMESTAMP"
lcPrimarySignature = "BOX_SIGNATURE_PRIMARY"
lcSecondarySignature = "BOX_SIGNATURE_SECONDARY"
lcSignatureVersion = "1"
lcSignatureAlgorithm = "HmacSHA256"
*  Retrieve configured keys from protected application configuration.
lcPrimaryKey = "BOX_PRIMARY_SIGNATURE_KEY"
lcSecondaryKey = "BOX_SECONDARY_SIGNATURE_KEY"
IF ((lcSignatureVersion <> "1") OR (lcSignatureAlgorithm <> "HmacSHA256")) THEN
    ? "Unsupported signature version or algorithm."
    RELEASE loRawBody
    CANCEL
ENDIF

loDelivered = CreateObject('Chilkat.CkDateTime')
lnSuccess = loDelivered.SetFromTimestamp(lcTimestamp)
IF (lnSuccess <> 1) THEN
    ? loDelivered.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    CANCEL
ENDIF

loNow = CreateObject('Chilkat.CkDateTime')
lnSuccess = loNow.SetFromCurrentSystemTime()
IF (lnSuccess <> 1) THEN
    ? loNow.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    CANCEL
ENDIF

lnAge = VAL(loNow.GetAsUnixTime(0)) - VAL(loDelivered.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 ((lnAge > 600) OR (lnAge < -60)) THEN
    ? "Delivery timestamp is outside the accepted window."
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    CANCEL
ENDIF

loSignedBytes = CreateObject('Chilkat.BinData')
lnSuccess = loSignedBytes.AppendBd(loRawBody)
IF (lnSuccess <> 1) THEN
    ? loSignedBytes.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    CANCEL
ENDIF

lnSuccess = loSignedBytes.AppendString(lcTimestamp,"utf-8")
IF (lnSuccess <> 1) THEN
    ? loSignedBytes.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    CANCEL
ENDIF

loMac = CreateObject('Chilkat.Crypt2')
loMac.MacAlgorithm = "hmac"
loMac.HashAlgorithm = "sha256"
loMac.EncodingMode = "base64"
lnSuccess = loMac.SetMacKeyString(lcPrimaryKey)
IF (lnSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    CANCEL
ENDIF

lcExpectedPrimary = loMac.MacBdENC(loSignedBytes)
IF (loMac.LastMethodSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    CANCEL
ENDIF

lnSuccess = loMac.SetMacKeyString(lcSecondaryKey)
IF (lnSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    CANCEL
ENDIF

lcExpectedSecondary = loMac.MacBdENC(loSignedBytes)
IF (loMac.LastMethodSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    CANCEL
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.
loRandom = CreateObject('Chilkat.Prng')
lcComparisonKey = loRandom.GenRandom(32,"base64")
IF (loRandom.LastMethodSuccess <> 1) THEN
    ? loRandom.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

lnSuccess = loMac.SetMacKeyEncoded(lcComparisonKey,"base64")
IF (lnSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

lcPExpected = loMac.MacStringENC(lcExpectedPrimary)
IF (loMac.LastMethodSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

lcPReceived = loMac.MacStringENC(lcPrimarySignature)
IF (loMac.LastMethodSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

lcSExpected = loMac.MacStringENC(lcExpectedSecondary)
IF (loMac.LastMethodSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

lcSReceived = loMac.MacStringENC(lcSecondarySignature)
IF (loMac.LastMethodSuccess <> 1) THEN
    ? loMac.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

lnValidPrimary = (lcPrimaryKey <> "") AND (lcPrimarySignature <> "") AND (lcPExpected = lcPReceived)
lnValidSecondary = (lcSecondaryKey <> "") AND (lcSecondarySignature <> "") AND (lcSExpected = lcSReceived)
IF ((lnValidPrimary = 0) AND (lnValidSecondary = 0)) THEN
    ? "Signature verification failed."
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    CANCEL
ENDIF

*  Parse JSON only after signature verification.
loWebhookEvent = CreateObject('Chilkat.JsonObject')
lnSuccess = loWebhookEvent.Load(loRawBody.GetString("utf-8"))
IF (lnSuccess <> 1) THEN
    ? loWebhookEvent.LastErrorText
    RELEASE loRawBody
    RELEASE loDelivered
    RELEASE loNow
    RELEASE loSignedBytes
    RELEASE loMac
    RELEASE loRandom
    RELEASE loWebhookEvent
    CANCEL
ENDIF

? "Verified event ID: " + loWebhookEvent.StringOf("id")
? "Trigger: " + loWebhookEvent.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.

RELEASE loRawBody
RELEASE loDelivered
RELEASE loNow
RELEASE loSignedBytes
RELEASE loMac
RELEASE loRandom
RELEASE loWebhookEvent