# About webhooks

A webhook (also called a web callback) allows your system to receive updates about your account to an HTTPS endpoint that you provide. When a supported event occurs, a notification is sent via HTTP `POST` method to the specified endpoint.

:::note
This guide applies to the Webhooks API in [v2](/docs/api/business#tag-webhooks-v2).

For the previous version of the API, check the API Reference: [Webhooks (v1) (deprecated)](/docs/api/business#tag-webhooks-v1-deprecated).
:::

## Supported event types

You can subscribe to the following event types:

- [`TransactionCreated`](#transaction-created-event)
- [`TransactionStateChanged`](#transaction-state-changed-event)
- [`PayoutLinkCreated`](#payout-link-created-event)
- [`PayoutLinkStateChanged`](#payout-link-state-changed-event)

To subscribe to selected event types, provide their names on webhook [creation](/docs/api/business#create-webhook) or [update](/docs/api/business#update-webhook).

### Default event types

If you don't specify which event types you want to subscribe to, you're automatically subscribed to the default event types: [`TransactionCreated`](#transaction-created-event) and [`TransactionStateChanged`](#transaction-state-changed-event).

## Supported operations

The Business API supports the following operations on webhooks:

- [Create a webhook](/docs/api/business#create-webhook) to start receiving selected events to a specified URL
- [Retrieve a list of webhooks](/docs/api/business#get-webhooks) to check what webhooks you have configured
- [Retrieve a specific webhook](/docs/api/business#get-webhook) by its ID
- [Delete a webhook](/docs/api/business#delete-webhook) and stop receiving events to your specified URL
- [Update a webhook](/docs/api/business#update-webhook) to change the URL or the events that you're subscribed to
- [Rotate a webhook signing secret](/docs/api/business#rotate-webhook-signing-secret)
- [Retrieve a list of failed webhook events](/docs/api/business#get-failed-webhook-events)

:::tip
For more information on how to work with webhooks, see [Manage webhooks](/docs/guides/manage-accounts/webhooks/manage-webhooks).
:::

## Webhook examples

See below the examples of different types of events that webhooks can receive.

### Transaction created event

See an example of the `TransactionCreated` event:

```json
{
  "event": "TransactionCreated",
  "timestamp": "2023-01-26T16:22:21.753463Z",
  "data": {
    "id": "63d2a8bd-8b67-a2de-b1d2-b58ee21d7073",
    "type": "transfer",
    "state": "pending",
    "request_id": "6a8b2ad9-d8b9-4348-9207-1c5737ccf11b",
    "created_at": "2023-01-26T16:22:21.765313Z",
    "updated_at": "2023-01-26T16:22:21.765313Z",
    "reference": "To John Doe",
    "legs": [
      {
        "leg_id": "63d2a8bd-8b67-a2de-0000-b58ee21d7073",
        "account_id": "05018b0d-e67c-4fec-bea6-415e9da9432c",
        "counterparty": {
          "id": "7e18625a-3e6c-4d4f-8429-216c25309a5f",
          "account_type": "external",
          "account_id": "ff29e658-f07f-4d81-bc0f-7ad0ff141357"
        },
        "amount": -10,
        "currency": "GBP",
        "description": "To Acme Corp"
      }
    ]
  }
}
```

:::details [See the field details]

| Field       | Description           | Type                                           | Format        |
| ----------- | --------------------- | ---------------------------------------------- | ------------- |
| `event`     | The event name.       | String                                         | Text          |
| `timestamp` | The event time.       | String                                         | ISO date/time |
| `data`      | The transaction data. | [Object](#data-object-for-transaction-created) | JSON          |

#### Data object for transaction created

The `data` object contains the following fields:

| Field                    | Description                                                                                         | Type                             | Format        |
| ------------------------ | --------------------------------------------------------------------------------------------------- | -------------------------------- | ------------- |
| `id`                     | The ID of transaction.                                                                              | String                           | UUID          |
| `type`                   | The transaction type.                                                                               | String                           | Text          |
| `state`                  | The transaction state: `pending`, `completed`, `declined` or `failed`.                              | String                           | Text          |
| `request_id`             | The request ID provided by the client.                                                              | String                           | Text          |
| `reason_code`            | The optional reason code for `declined` or `failed` transaction state.                              | String                           | Text          |
| `created_at`             | The date and time the transaction was created.                                                      | String                           | ISO date/time |
| `updated_at`             | The date and time the transaction was last updated.                                                 | String                           | ISO date/time |
| `completed_at`           | The date and time the transaction was completed, mandatory for `completed` state only.              | String                           | ISO date/time |
| `scheduled_for`          | The optional date the transaction was scheduled for.                                                | String                           | ISO date      |
| `reference`              | The payment reference provided by the user.                                                         | String                           | Text          |
| `related_transaction_id` | The ID of the related transaction. Only applicable when `type=refund`.                              | String                           | UUID          |
| `legs`                   | The legs of a transaction. There are 2 legs between your Revolut accounts and 1 leg in other cases. | [Array of objects](#legs-object) | JSON          |

#### Legs object

An object in the `legs` array contains the following fields:

| Field           | Description                                                                     | Type                           | Format                     |
| --------------- | ------------------------------------------------------------------------------- | ------------------------------ | -------------------------- |
| `leg_id`        | The ID of the leg.                                                              | String                         | UUID                       |
| `account_id`    | The ID of the account the transaction is associated with.                       | String                         | UUID                       |
| `counterparty`  | The counterparty object.                                                        | [Object](#counterparty-object) | JSON                       |
| `amount`        | The transaction amount.                                                         | Number                         | Decimal                    |
| `fee`           | The optional transaction fee amount.                                            | Number                         | Decimal                    |
| `currency`      | The transaction currency.                                                       | String                         | 3-letter ISO currency code |
| `bill_amount`   | The billing amount for cross-currency payments.                                 | String                         | Decimal                    |
| `bill_currency` | The billing currency for cross-currency payments.                               | String                         | 3-letter ISO currency code |
| `description`   | The transaction leg purpose.                                                    | String                         | Text                       |
| `balance`       | The total balance of the account the transaction is associated with (optional). | Number                         | Decimal                    |

#### Counterparty object

A `counterparty` object contains the following fields:

| Field          | Description                                                      | Type   | Format |
| -------------- | ---------------------------------------------------------------- | ------ | ------ |
| `id`           | The counterparty ID.                                             | String | UUID   |
| `account_id`   | The counterparty account ID.                                     | String | UUID   |
| `account_type` | The type of counterparty account: `self`, `revolut`, `external`. | String | Text   |

:::

### Transaction state changed event

See an example of the `TransactionStateChanged` event:

```json
{
  "event": "TransactionStateChanged",
  "timestamp": "2023-04-06T12:21:49.865Z",
  "data": {
    "id": "9a6434d8-3581-4faa-988b-48875e785be7",
    "request_id": "6a8b2ad9-d8b9-4348-9207-1c5737ccf11b",
    "old_state": "pending",
    "new_state": "reverted"
  }
}
```

:::details [See the field details]

| Field       | Description           | Type                                                 | Format        |
| ----------- | --------------------- | ---------------------------------------------------- | ------------- |
| `event`     | The event name.       | String                                               | Text          |
| `timestamp` | The event time.       | String                                               | ISO date/time |
| `data`      | The transaction data. | [Object](#data-object-for-transaction-state-changed) | JSON          |

#### Data object for transaction state changed

The `data` object contains the following fields:

| Field        | Description                            | Type   | Format |
| ------------ | -------------------------------------- | ------ | ------ |
| `id`         | The ID of the updated transaction.     | String | UUID   |
| `request_id` | The request ID provided by the client. | String | Text   |
| `old_state`  | The previous state of the transaction. | String | Text   |
| `new_state`  | The new state of the transaction.      | String | Text   |

:::

### Payout link created event

See an example of the `PayoutLinkCreated` event:

```json
{
  "event": "PayoutLinkCreated",
  "timestamp": "2023-07-05T16:22:21.753463Z",
  "data": {
    "id": "3b9087bb-290a-4321-8ef6-96b9a2f071e8",
    "state": "created",
    "request_id": "ff5fadff-aae1-4848-87f8-53d515eea3b5"
  }
}
```

:::details [See the field details]

| Field       | Description           | Type                                           | Format        |
| ----------- | --------------------- | ---------------------------------------------- | ------------- |
| `event`     | The event name.       | String                                         | Text          |
| `timestamp` | The event time.       | String                                         | ISO date/time |
| `data`      | The transaction data. | [Object](#data-object-for-payout-link-created) | JSON          |

#### Data object for payout link created

The `data` object contains the following fields:

| Field        | Description                                                                                                                                                                                  | Type   | Format |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
| `id`         | The ID of the link.                                                                                                                                                                          | String | UUID   |
| `state`      | The [state](/docs/guides/manage-accounts/transfers/payout-links#states) that the link is in: `created`, `failed`, `awaiting`, `active`, `expired`, `cancelled`, `processing` or `processed`. | String | Text   |
| `request_id` | The request ID provided by the client.                                                                                                                                                       | String | Text   |

:::

### Payout link state changed event

See an example of the `PayoutLinkStateChanged` event:

```json
{
  "event": "PayoutLinkStateChanged",
  "timestamp": "2023-07-06T10:12:38.568Z",
  "data": {
    "id": "0e1a8d4b-1d1e-457d-9f10-3e7007a82ea8",
    "request_id": "2e2837f5-828d-473f-85e8-5614afd3f8e8",
    "old_state": "active",
    "new_state": "processed"
  }
}
```

:::details [See the field details]

| Field       | Description           | Type                                                 | Format        |
| ----------- | --------------------- | ---------------------------------------------------- | ------------- |
| `event`     | The event name.       | String                                               | Text          |
| `timestamp` | The event time.       | String                                               | ISO date/time |
| `data`      | The transaction data. | [Object](#data-object-for-payout-link-state-changed) | JSON          |

#### Data object for payout link state changed

The `data` object contains the following fields:

| Field        | Description                            | Type   | Format |
| ------------ | -------------------------------------- | ------ | ------ |
| `id`         | The ID of the updated link.            | String | UUID   |
| `request_id` | The request ID provided by the client. | String | Text   |
| `old_state`  | The previous state of the link.        | String | Text   |
| `new_state`  | The new state of the link.             | String | Text   |

:::

## Retries and errors

If the webhook URL returns an HTTP error response and the delivery of the webhook fails, **Revolut will retry the webhook event 3 more times, each with a 10-minute interval.**

If, despite the retries, the event is still not delivered, you can check it by [retrieving the list of failed webhook events](/docs/guides/manage-accounts/webhooks/manage-webhooks#retrieve-a-list-of-failed-webhook-events).

:::warning
Webhook notifications might occasionally be sent more than once. Make sure that your webhook endpoints are idempotent by checking if you have already received a given webhook event.
:::

:::warning
We cannot guarantee the delivery of events in the order in which they are generated, so make sure your server does not rely on the temporal order of events.

For example, for a transaction, normally, you should first receive the `TransactionCreated` webhook event, and then the `TransactionStateChanged` event.
However, if the `TransactionCreated` status isn't sent successfully in the first place, it's moved to the queue to be resent in the next few minutes.
Before then, if the `TransactionStateChanged` status is already sent, you first get `TransactionStateChanged`, and then `TransactionCreated`.
:::

## IP Allowlisting

Webhook notifications are sent from the following IP addresses, in case you need to allowlist them:

- ![Production]

  - `35.246.21.235`
  - `34.89.70.170`

- ![Sandbox]

  - `35.242.130.242`
  - `35.242.162.241`

## Security

Each webhook notification contains the following headers:

- **Revolut-Request-Timestamp**: [Timestamp](#timestamp-validation) of the webhook event, for example: `1683650202360`.
- **Revolut-Signature**: Signature of the request payload. Contains the current version of the signature generating algorithm, and the hexadecimal-encoded signature itself, for example: `v1=09a9989dd8d9282c1d34974fc730f5cbfc4f4296941247e90ae5256590a11e8c`.

  :::note
  The `Revolut-Signature` header can contain **multiple signatures** if multiple [signing secrets](#webhook-signing-secret) are active at a given moment.
  If that's the case, they are separated by a comma. For example:

  ```
  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](/docs/guides/manage-accounts/webhooks/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/guides/manage-accounts/webhooks/manage-webhooks#create-a-webhook), and it only changes on the [secret's rotation](/docs/guides/manage-accounts/webhooks/manage-webhooks#rotate-a-webhook-signing-secret).
After you've created a webhook, you can retrieve the signing secret when you [retrieve a specific webhook](/docs/guides/manage-accounts/webhooks/manage-webhooks#retrieve-a-specific-webhook)'s details.

If you suspect that your webhook signing secret has been compromised, you can rotate it.

On rotation, you can pass the [optional `expiration_period` parameter](/docs/api/business#rotate-webhook-signing-secret#request) for the old secret to remain valid until the expiration period has passed. Otherwise, it is invalidated immediately.

In the period when multiple signing secrets remain valid, multiple [signatures](#security) are sent.

### 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 in which 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).