Each webhook notification contains the following headers:
Header parameter | Description | Format |
---|---|---|
Revolut-Request-Timestamp | UNIX timestamp of the webhook event. Example value: 1683650202360 | String |
Revolut-Signature | Signature of the request payload. Contains the current version of the signature-generating algorithm and the hexadecimal-encoded signature. Example value: v1=09a9989dd8d9282c1d34974fc730f5cbfc4f4296941247e90ae5256590a11e8c | String |
If multiple signing secrets are active at a given moment, the Revolut-Signature
header can contain multiple signatures separated by a comma:
Revolut-Signature: v1=4fce70bda66b2e713be09fbb7ab1b31b0c8976ea4eeb01b244db7b99aa6482cb,v1=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
To ensure that a webhook request originates from Revolut and not a third party, we recommend that you verify the request's signature using a signing secret.
Revolut uses the HMAC SHA-256 algorithm to sign its webhooks. The secret is generated on webhook creation, and it only changes on the secret's rotation. After you've created a webhook, you can retrieve the signing secret when you retrieve a specific webhook's details.
If you suspect that your webhook signing secret has been compromised, it's recommended to rotate it.
On rotation, you can pass the optional expiration_period
parameter for the old secret to remain valid until the expiration period passes. Otherwise, it is invalidated immediately.
During the expiration period, when multiple signing secrets remain valid, our API sends multiple signatures.
You can mitigate replay attacks in your webhooks by applying timestamp validation.
For each webhook event, Revolut sends a Revolut-Request-Timestamp
header with the exact date-time when it was delivered.
To validate the event, make sure that the Revolut-Request-Timestamp
date-time is within a 5-minute time tolerance of the current universal time (UTC).
Follow these steps to verify the signature for the webhook's request payload.
To compute payload_to_sign
, concatenate the following data, separating each item with a full stop (.
):
v1
)Revolut-Request-Timestamp
headerpayload_to_sign = {version}.{Revolut-Request-Timestamp}.{raw-payload}
An example of payload_to_sign
might look like this:
v1.1683650202360.{"event": "ORDER_COMPLETED","order_id": "9fc01989-3f61-4484-a5d9-ffe768531be9","merchant_order_ext_ref": "Test #3928"}
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.
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:
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 = '{"event": "ORDER_COMPLETED","order_id": "9fc01989-3f61-4484-a5d9-ffe768531be9","merchant_order_ext_ref": "Test #3928"}'
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)
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.
We recommend that you test your implementation of webhook signature validation in the Sandbox environment before implementing it in production.