React Native Requires Chilkat v11.5.0+
React Native
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 React Native Downloads
import { BinData, CkDateTime, Crypt2, JsonObject, Prng } from '@chilkat/react-native'
function chilkatExample() {
// Requires Chilkat 11.5.0 or later and a previously unlocked Chilkat bundle.
// See Global Unlock Sample.
// Guide: Box API Guide.
// Save the exact HTTP body bytes to this file, without JSON reserialization.
const rawBody = new BinData();
try {
rawBody.loadFile('qa_data/box_webhook_body.bin');
} catch {
console.log(rawBody.lastErrorText);
return;
}
// Read these fields case-insensitively from the incoming request headers.
// For this offline example, enter the captured header values.
const timestamp = 'BOX_DELIVERY_TIMESTAMP';
const primarySignature = 'BOX_SIGNATURE_PRIMARY';
const secondarySignature = 'BOX_SIGNATURE_SECONDARY';
const signatureVersion = '1';
const signatureAlgorithm = 'HmacSHA256';
// Retrieve configured keys from protected application configuration.
const primaryKey = 'BOX_PRIMARY_SIGNATURE_KEY';
const secondaryKey = 'BOX_SECONDARY_SIGNATURE_KEY';
if ((signatureVersion !== '1') || (signatureAlgorithm !== 'HmacSHA256')) {
console.log('Unsupported signature version or algorithm.');
return;
}
const delivered = new CkDateTime();
try {
delivered.setFromTimestamp(timestamp);
} catch {
console.log(delivered.lastErrorText);
return;
}
const now = new CkDateTime();
try {
now.setFromCurrentSystemTime();
} catch {
console.log(now.lastErrorText);
return;
}
const age = now.getAsUnixTime(false) - 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) || (age < -60)) {
console.log('Delivery timestamp is outside the accepted window.');
return;
}
const signedBytes = new BinData();
try {
signedBytes.appendBd(rawBody);
} catch {
console.log(signedBytes.lastErrorText);
return;
}
try {
signedBytes.appendString(timestamp, 'utf-8');
} catch {
console.log(signedBytes.lastErrorText);
return;
}
const mac = new Crypt2();
mac.macAlgorithm = 'hmac';
mac.hashAlgorithm = 'sha256';
mac.encodingMode = 'base64';
try {
mac.setMacKeyString(primaryKey);
} catch {
console.log(mac.lastErrorText);
return;
}
let expectedPrimary: string;
try {
expectedPrimary = mac.macBdENC(signedBytes);
} catch {
console.log(mac.lastErrorText);
return;
}
try {
mac.setMacKeyString(secondaryKey);
} catch {
console.log(mac.lastErrorText);
return;
}
let expectedSecondary: string;
try {
expectedSecondary = mac.macBdENC(signedBytes);
} catch {
console.log(mac.lastErrorText);
return;
}
// 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.
const random = new Prng();
let comparisonKey: string;
try {
comparisonKey = random.genRandom(32, 'base64');
} catch {
console.log(random.lastErrorText);
return;
}
try {
mac.setMacKeyEncoded(comparisonKey, 'base64');
} catch {
console.log(mac.lastErrorText);
return;
}
let pExpected: string;
try {
pExpected = mac.macStringENC(expectedPrimary);
} catch {
console.log(mac.lastErrorText);
return;
}
let pReceived: string;
try {
pReceived = mac.macStringENC(primarySignature);
} catch {
console.log(mac.lastErrorText);
return;
}
let sExpected: string;
try {
sExpected = mac.macStringENC(expectedSecondary);
} catch {
console.log(mac.lastErrorText);
return;
}
let sReceived: string;
try {
sReceived = mac.macStringENC(secondarySignature);
} catch {
console.log(mac.lastErrorText);
return;
}
const validPrimary = (primaryKey !== '') && ((primarySignature !== '') && (pExpected === pReceived));
const validSecondary = (secondaryKey !== '') && ((secondarySignature !== '') && (sExpected === sReceived));
if ((!validPrimary) && (!validSecondary)) {
console.log('Signature verification failed.');
return;
}
// Parse JSON only after signature verification.
const webhookEvent = new JsonObject();
try {
webhookEvent.load(rawBody.getString('utf-8'));
} catch {
console.log(webhookEvent.lastErrorText);
return;
}
console.log(`Verified event ID: ${webhookEvent.stringOf('id')}`);
console.log(`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.
}