# Webhook signature verification

## Security

Each webhook notification contains the following headers:

| Header parameter            | Description                                                                                                                                                                                                                      | Format |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| `Revolut-Request-Timestamp` | [UNIX timestamp](#timestamp-validation) 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 |

:::info
If multiple [signing secrets](#webhook-signing-secret) are active at a given moment, the `Revolut-Signature` header can contain **multiple signatures** separated by a comma:

```shell
Revolut-Signature: v1=4fce70bda66b2e713be09fbb7ab1b31b0c8976ea4eeb01b244db7b99aa6482cb,v1=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
```

:::

### Webhook signing secret

To ensure that a webhook request originates from Revolut and not a third party, we recommend that you [verify the request's signature](#verify-the-payload-signature) using a signing secret.

Revolut uses the HMAC SHA-256 algorithm to sign its webhooks.
The secret is generated on [webhook creation](/docs/api/merchant#create-webhook), and it only changes on the [secret's rotation](/docs/api/merchant#rotate-webhook-signing-secret).
After you've created a webhook, you can retrieve the signing secret when you [retrieve a specific webhook](/docs/api/merchant#retrieve-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](/docs/api/merchant#rotate-webhook-signing-secret) 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.

### Timestamp validation

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).

## 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 (`.`):

1. The [version](#security) of the signature-generating algorithm (`v1`)
1. The [`Revolut-Request-Timestamp` header](#security)
1. The raw webhook payload without whitespaces

```shell
payload_to_sign = {version}.{Revolut-Request-Timestamp}.{raw-payload}
```

An example of `payload_to_sign` might look like this:

```shell
v1.1683650202360.{"event": "ORDER_COMPLETED","order_id": "9fc01989-3f61-4484-a5d9-ffe768531be9","merchant_order_ext_ref": "Test #3928"}
```

:::warning
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](#webhook-signing-secret) as the key
- The payload to sign (prepared in the [previous step](#1-prepare-the-payload-to-sign)) as the message

You can use the following Python implementation for reference:

```python [Computation of the expected signature in Python] {10}
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)
```

### 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.

:::warning
We recommend that you test your implementation of webhook signature validation in the [Sandbox environment](https://sandbox-business.revolut.com) before implementing it in production.
:::