Verify the payload signature
Follow these steps to verify the signature for the webhook's request payload.
1. Prepare the payload to sign
To compute payload_to_sign, concatenate the following data, separating each item with a full stop (.):
- The version of the signature-generating algorithm (
v1) - The
Revolut-Request-Timestampheader - The raw webhook payload without whitespaces
payload_to_sign = {version}.{timestamp}.{raw_payload}
An example of payload_to_sign might look like this:
v1.1683650202360.{"data":{"id":"645a7696-1234-aa47-1234-cbae0449cc46","new_state":"completed","old_state":"pending","request_id":"app_charges-9f5d5eb3-1234-1234-1234-3914763e0bcb"},"event":"TransactionStateChanged","timestamp":"2023-05-09T16:36:38.028960Z"}
The signature is sensitive to any modifications, meaning even a small change in the body will result in a completely different signature. Therefore, it is crucial not to alter the body, especially before the verification.
2. Compute the expected signature
To compute the expected signature, you need to concatenate the version of the signature-generating algorithm (v1) with the hash-based message authentication code (HMAC). Separate them with the equals character (=).
To compute the HMAC, use the SHA256 hash function and:
- The signing secret for the webhook as the key
- The payload to sign (prepared in the previous step) as the message
You can use the following Python implementation for reference:
import hmac
import hashlib
signing_secret = 'wsk_r59a4HfWVAKycbCaNO1RvgCJec02gRd8' #Obtained on webhook creation/details retrieval
raw_payload = '{"data":{"id":"645a7696-22f3-aa47-9c74-cbae0449cc46","new_state":"completed","old_state":"pending","request_id":"app_charges-9f5d5eb3-1e06-46c5-b1c0-3914763e0bcb"},"event":"TransactionStateChanged","timestamp":"2023-05-09T16:36:38.028960Z"}'
timestamp = '1683650202360'
payload_to_sign = 'v1.' + timestamp + '.' + raw_payload #Prepared in Step 1
signature = 'v1=' + hmac.new(bytes(signing_secret , 'utf-8'), msg = bytes(payload_to_sign , 'utf-8'), digestmod = hashlib.sha256).hexdigest()
print(signature)
3. Compare signatures
Once you've computed the expected signature, compare it with the signature obtained in the Revolut-Signature header of the webhook notification.
The computed signature must match exactly the signature (or one of the multiple signatures) sent in that header.
To ensure the accuracy of your implementation, you can validate it by checking against the test data that we prepared:
- Revolut-Signature header:
v1=bca326fb378d0da7f7c490ad584a8106bab9723d8d9cdd0d50b4c5b3be3837c0 - Revolut-Request-Timestamp header:
1683650202360 - payload_to_sign:
v1.1683650202360.{"data":{"id":"645a7696-22f3-aa47-9c74-cbae0449cc46","new_state":"completed","old_state":"pending","request_id":"app_charges-9f5d5eb3-1e06-46c5-b1c0-3914763e0bcb"},"event":"TransactionStateChanged","timestamp":"2023-05-09T16:36:38.028960Z"} - signing_secret:
wsk_r59a4HfWVAKycbCaNO1RvgCJec02gRd8
We recommend that you test your implementation of webhook signature validation in the Sandbox environment before implementing it in production.