Signature Verification
Signature Verification
Webhook signatures help ensure that a webhook payload was sent by Box and was not tampered with in transit. Signatures greatly reduce the likelihood of successful man-in-the-middle or replay attacks.
When configured, Box generates a cryptographic digest of the notification's body and attaches it the header of the webhook payload. When your application receives the payload, it is advised to verify the signatures by calculating the same digest and comparing it to the one received. If the digests do not match, the payload should not be trusted.
An extra level of protection can be achieved by frequently changing the signature keys. To enable a smooth transition between the old and new keys, we support two simultaneous signature keys.
Signature configuration
In order to attach signatures to an application's notifications, you must first generate signature keys for the application.
To configure your application's keys, navigate to the application in the Developer Console. Click on the Webhooks tab and locate the Generate Key buttons.
Once generating the primary or secondary key, copy the value, as you will need
it to verify the webhook payloads. Every webhook will now include a
BOX-SIGNATURE-PRIMARY
and BOX-SIGNATURE-SECONDARY
header payload.
Signature verification with SDKs
Although it is possible to verify signatures manually, methods are provided for your convenience in our official Box SDKs.
using Box.V2.Managers;
var body = "{\"type\":\"webhook_event\",\"webhook\":{\"id\":\"1234567890\"},\"trigger\":\"FILE.UPLOADED\",\"source\":{\"id\":\"1234567890\",\"type\":\"file\",\"name\":\"Test.txt\"}}";
var headers = new Dictionary<string, string>()
{
{ "box-delivery-id", "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f" },
{ "box-delivery-timestamp", "2020-01-01T00:00:00-07:00" },
{ "box-signature-algorithm", "HmacSHA256" } ,
{ "box-signature-primary", "6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=" },
{ "box-signature-secondary", "v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=" },
{ "box-signature-version", "1" }
};
var primaryKey = "Fd28OJrZ8oNxkgmS7TbjXNgrG8v";
var secondaryKey = "KWkROAOiof4zhYUHbAmiVn63cMj"
bool isValid = BoxWebhooksManager.VerifyWebhook(
deliveryTimestamp: headers["box-delivery-timestamp"],
signaturePrimary: headers["box-signature-primary"],
signatureSecondary: headers["box-signature-secondary"],
payload: body,
primaryWebhookKey: primaryKey,
secondaryWebhookKey: secondaryKey
);
// Webhook message contents are shown for demonstration purposes
// Normally these would come from your HTTP handler
// Webhook message HTTP body
String messagePayload = "{"
+ "\"type\":\"webhook_event","
+ "\"webhook\":{"
+ "\"id\":\"1234567890\""
+ "},"
+ "\"trigger\":\"FILE.UPLOADED\","
+ "\"source\":{"
+ "\"id\":\"1234567890\","
+ "\"type\":\"file\","
+ "\"name\":\"Test.txt\""
+ "}}";
// Webhook message HTTP headers
Map<String, String> messageHeaders = new HashMap<String, String>();
headers.put("BOX-DELIVERY-ID", "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f");
headers.put("BOX-DELIVERY-TIMESTAMP", "2020-01-01T00:00:00-07:00");
headers.put("BOX-SIGNATURE-ALGORITHM", "HmacSHA256");
headers.put("BOX-SIGNATURE-PRIMARY", "6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=");
headers.put("BOX-SIGNATURE-SECONDARY", "v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=");
headers.put("BOX-SIGNATURE-VERSION", "1");
// Your application's webhook keys, obtained from the Box Developer Console
String primaryKey = "4py2I9eSFb0ezXH5iPeQRcFK1LRLCdip";
String secondaryKey = "Aq5EEEjAu4ssbz8n9UMu7EerI0LKj2TL";
BoxWebHookSignatureVerifier verifier = new BoxWebHookSignatureVerifier(primaryKey, secondaryKey);
boolean isValidMessage = verifier.verify(
headers.get("BOX-SIGNATURE-VERSION"),
headers.get("BOX-SIGNATURE-ALGORITHM"),
headers.get("BOX-SIGNATURE-PRIMARY"),
headers.get("BOX-SIGNATURE-SECONDARY"),
messagePayload,
headers.get("BOX-DELIVERY-TIMESTAMP")
);
if (isValidMessage) {
// Message is valid, handle it
} else {
// Message is invalid, reject it
}
body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}'
headers = {
'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f',
'box-delivery-timestamp': '2020-01-01T00:00:00-07:00',
'box-signature-algorithm': 'HmacSHA256',
'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=',
'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=',
'box-signature-version': '1',
}
is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key)
print(f'The webhook message is validated to: {is_validated}')
const BoxSDK = require('box-node-sdk');
let body = '{"type":"webhook_event","webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}',
headers = {
'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f',
'box-delivery-timestamp': '2020-01-01T00:00:00-07:00',
'box-signature-algorithm': 'HmacSHA256',
'box-signature-primary': '6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=',
'box-signature-secondary': 'v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=',
'box-signature-version': '1'
},
primaryKey = 'SamplePrimaryKey',
secondaryKey = 'SampleSecondaryKey';
let isValid = BoxSDK.validateWebhookMessage(body, headers, primaryKey, secondaryKey);
if (isValid) {
// message is valid, accept
} else {
// message is NOT valid, reject
}
Manual signature verification
The following steps describe the basics of how to verify a signature.
Timestamp validation
Validate the timestamp in the BOX-DELIVERY-TIMESTAMP
header of the payload is
not older than ten minutes.
var timestamp = headers['BOX-DELIVERY-TIMESTAMP'];
var date = Date.parse(timestamp);
var expired = Date.now() - date > 10*60*1000;
import dateutil.parser
import pytz
import datetime
timestamp = headers["BOX-DELIVERY-TIMESTAMP"]
date = dateutil.parser.parse(timestamp).astimezone(pytz.utc)
now = datetime.datetime.now(pytz.utc)
delta = datetime.timedelta(minutes=10)
expiry_date = now - deltaMinutes
expired = date >= expiry_date
Calculate HMAC signature
Calculate the HMAC of the payload using either of the two configured signatures for the application in the Developer Console.
Ensure you append the bytes of the body of the payload first and then the bytes
of the timestamp found in the BOX-DELIVERY-TIMESTAMP
header.
var crypto = require('crypto');
var primaryKey = '...';
var secondaryKey = '...';
var payload = '{"type":"webhook_event"...}';
var hmac1 = crypto.createHmac('sha256', primaryKey);
hmac1.update(payload);
hmac1.update(timestamp);
var hmac2 = crypto.createHmac('sha256', secondaryKey);
hmac2.update(payload);
hmac2.update(timestamp);
import hmac
import hashlib
primary_key = '...'
secondary_key = '...'
payload = "{\"type\":\"webhook_event\"...}"
bytes = bytes(payload, 'utf-8') + bytes(timestamp, 'utf-8')
hmac1 = hmac.new(primary_key, bytes, hashlib.sha256).digest()
hmac2 = hmac.new(secondary_key, bytes, hashlib.sha256).digest()
Base64 Conversion
Convert the HMAC to a Base64
encoded digest.
var digest1 = hmac1.digest('base64');
var digest2 = hmac2.digest('base64');
import base64
digest1 = base64.b64encode(hmac1)
digest2 = base64.b64encode(hmac2)
Signature comparison
Compare the encoded digest with the value of the
BOX-SIGNATURE-PRIMARY
or BOX-SIGNATURE-SECONDARY
headers.
Ensure the value of the BOX-SIGNATURE-PRIMARY
header
to the digest created with the primary key and the value of the
BOX-SIGNATURE-SECONDARY
header to the digest created with the secondary key.
var signature1 = headers['BOX-SIGNATURE-SECONDARY'];
var signature2 = headers['BOX-SIGNATURE-PRIMARY'];
var primarySignatureValid = digest1 === signature1
var secondarySignatureValid = digest2 === signature2
var valid = !expired && (primarySignatureValid || secondarySignatureValid)
signature1 = headers["BOX-SIGNATURE-SECONDARY"]
signature2 = headers["BOX-SIGNATURE-PRIMARY"]
primary_sig_valid = digest1 === signature1
secondary_sig_valid = digest2 === signature2
valid = !expired && (primary_sig_valid || secondary_sig_valid)
Rotate signatures
When enabled, Box will always sends two signatures with every webhook payload. Your application can trust a payload as long as at least one of its signatures is valid. When updating one signature key at a time your application will always receive a payload with at least one valid signature.
Rotation steps
These instructions assume that you have already created a primary and secondary key in the Developer Console and are ready to replace either of them.
By following these steps you can configure your application with two new keys without any conflicts.
- Change the primary key in the Developer Console by clicking the "Reset" button.
- Update your application with the new primary key. Your application might still receive notifications with the old primary key but your webhooks should still be processed correctly since the secondary key is still valid.
- Once you are confident that no webhooks with the old primary key are in-flight, you can update the secondary key with the same process.