Manual webhook signature verification
For API v3 users, we recommend you use the built-in support for webhook verification in our client libraries.
If you're unable to do so (for example, you're using API v2), you can follow the instructions on this page.
Each webhook event will have an
X-SHA2-Signature header. This contains the
webhook event’s signature in hexadecimal format.
Make sure you use the raw event request body. If you parse it from JSON first, the fields may be reordered.
You can get the expected signature of the webhook by computing the HMAC of the event request body using the SHA256 algorithm, using the webhook’s secret token as the key.
A webhook event is valid if the signature from the header is equal to the expected signature you compute for it. Make sure signatures are both in binary, or both in hexadecimal, before comparing.
You should use a constant time equality function to prevent timing attacks. A timing attack is where a malicious user measures the small time differences taken to compare the signatures over many requests, to eventually work out the expected signature for a webhook event. A constant time equality function prevents this by always taking the same amount of time when comparing strings of a particular length.
You may find the following pseudocode useful:
def verify_webhook_event_signature(webhook_event_request) signature_header_hexadecimal = webhook_event_request.get_header("x-sha2-signature") # Make sure to use the raw event body and not to convert it to an object first. raw_event_body = webhook_event_request.body_as_string # Compute the HMAC using the SHA256 algorithm and using your webhook's token as the key. expected_signature = hmac(algorithm="SHA256", key=webhook_token, data=raw_event_body) # Make sure signatures are both in binary, or both in hexadecimal, before comparing. signature_header = decode_hexadecimal(signature_header_hexadecimal) # Use a constant time equality function to prevent timing attacks. if constant_time_equals(signature_header, expected_signature) # It is now safe to parse the event body. return parse_json(raw_event_body) else # Handle an invalid webhook event. error("Invalid webhook event")