Lazarus Pascal Requires Chilkat v11.5.0+
Lazarus Pascal
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 Lazarus Pascal Downloads
program ChilkatDemo;
// Demonstrates using the Chilkat Pascal wrapper via the C bridge DLL.
// Builds as a console application under Lazarus (FPC) or Delphi.
{$IFDEF FPC}
{$MODE DELPHI}
{$ENDIF}
{$APPTYPE CONSOLE}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
SysUtils,
CkDllLoader,
Chilkat.JsonObject,
Chilkat.BinData,
Chilkat.Prng,
Chilkat.CkDateTime,
Chilkat.Crypt2;
// ---------------------------------------------------------------------------
procedure RunDemo;
var
success: Boolean;
rawBody: TBinData;
timestamp: string;
primarySignature: string;
secondarySignature: string;
signatureVersion: string;
signatureAlgorithm: string;
primaryKey: string;
secondaryKey: string;
delivered: TCkDateTime;
now: TCkDateTime;
age: Integer;
signedBytes: TBinData;
mac: TCrypt2;
expectedPrimary: string;
expectedSecondary: string;
random: TPrng;
comparisonKey: string;
pExpected: string;
pReceived: string;
sExpected: string;
sReceived: string;
validPrimary: Boolean;
validSecondary: Boolean;
webhookEvent: TJsonObject;
begin
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 := TBinData.Create;
success := rawBody.LoadFile('qa_data/box_webhook_body.bin');
if (success <> True) then
begin
WriteLn(rawBody.LastErrorText);
Exit;
end;
// 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 <> '1') or (signatureAlgorithm <> 'HmacSHA256')) then
begin
WriteLn('Unsupported signature version or algorithm.');
Exit;
end;
delivered := TCkDateTime.Create;
success := delivered.SetFromTimestamp(timestamp);
if (success <> True) then
begin
WriteLn(delivered.LastErrorText);
Exit;
end;
now := TCkDateTime.Create;
success := now.SetFromCurrentSystemTime();
if (success <> True) then
begin
WriteLn(now.LastErrorText);
Exit;
end;
age := StrToInt(now.GetAsUnixTime(False)) - StrToInt(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 > 600) or (age < -60)) then
begin
WriteLn('Delivery timestamp is outside the accepted window.');
Exit;
end;
signedBytes := TBinData.Create;
success := signedBytes.AppendBd(rawBody);
if (success <> True) then
begin
WriteLn(signedBytes.LastErrorText);
Exit;
end;
success := signedBytes.AppendString(timestamp,'utf-8');
if (success <> True) then
begin
WriteLn(signedBytes.LastErrorText);
Exit;
end;
mac := TCrypt2.Create;
mac.MacAlgorithm := 'hmac';
mac.HashAlgorithm := 'sha256';
mac.EncodingMode := 'base64';
success := mac.SetMacKeyString(primaryKey);
if (success <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
expectedPrimary := mac.MacBdENC(signedBytes);
if (mac.LastMethodSuccess <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
success := mac.SetMacKeyString(secondaryKey);
if (success <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
expectedSecondary := mac.MacBdENC(signedBytes);
if (mac.LastMethodSuccess <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
// 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 := TPrng.Create;
comparisonKey := random.GenRandom(32,'base64');
if (random.LastMethodSuccess <> True) then
begin
WriteLn(random.LastErrorText);
Exit;
end;
success := mac.SetMacKeyEncoded(comparisonKey,'base64');
if (success <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
pExpected := mac.MacStringENC(expectedPrimary);
if (mac.LastMethodSuccess <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
pReceived := mac.MacStringENC(primarySignature);
if (mac.LastMethodSuccess <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
sExpected := mac.MacStringENC(expectedSecondary);
if (mac.LastMethodSuccess <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
sReceived := mac.MacStringENC(secondarySignature);
if (mac.LastMethodSuccess <> True) then
begin
WriteLn(mac.LastErrorText);
Exit;
end;
validPrimary := (primaryKey <> '') and (primarySignature <> '') and (pExpected = pReceived);
validSecondary := (secondaryKey <> '') and (secondarySignature <> '') and (sExpected = sReceived);
if ((validPrimary = False) and (validSecondary = False)) then
begin
WriteLn('Signature verification failed.');
Exit;
end;
// Parse JSON only after signature verification.
webhookEvent := TJsonObject.Create;
success := webhookEvent.Load(rawBody.GetString('utf-8'));
if (success <> True) then
begin
WriteLn(webhookEvent.LastErrorText);
Exit;
end;
WriteLn('Verified event ID: ' + webhookEvent.StringOf('id'));
WriteLn('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.
rawBody.Free;
delivered.Free;
now.Free;
signedBytes.Free;
mac.Free;
random.Free;
webhookEvent.Free;
end;
// ---------------------------------------------------------------------------
begin
try
RunDemo;
except
on E: Exception do
WriteLn('Unhandled exception: ', E.ClassName, ': ', E.Message);
end;
WriteLn;
{$IFDEF MSWINDOWS}
WriteLn('Press Enter to exit...');
ReadLn;
{$ENDIF}
end.