Guides • Manage Accounts
Work with webhooks

Work with 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.

Supported event types

The following events are supported:

  • TransactionCreated
  • TransactionStateChanged
note

You cannot choose a specific event type to subscribe to. If you set up a webhook, a notification is sent for each supported event.

Set up a webhook

You can use the Business API to set up a webhook URL to receive a notification on every supported event:

note

You can set up a maximum of 10 webhook URLs.

curl -X POST https://b2b.revolut.com/api/2.0/webhooks \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer <your access token>" \
-d '{
"url": "https://example.com/webhook"
}'

Include the following request parameter:

FieldDescriptionFormat
urlValid webhook URL to which event notifications are sent. The supported protocol is HTTPS.URL

Manage webhooks

You can also use the Business API to manage your webhooks:

Webhook examples

See below the examples of the different types of webhooks:

Transaction created event

See an example of a TransactionCreated event:

{
"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"
}
]
}
}
    See the field details
FieldDescriptionFormat
eventThe event name.Text
timestampThe event time.ISO date/time
dataThe transaction data.Object

Data object for transaction created

The data object contains the following fields:

FieldDescriptionFormat
idThe ID of transaction.UUID
typeThe transaction type.Text
stateThe transaction state: pending, completed, declined or failed.Text
request_idThe request ID provided by the client.Text
reason_codeThe optional reason code for declined or failed transaction state.Text
created_atThe date and time the transaction was created.ISO date/time
updated_atThe date and time the transaction was last updated.ISO date/time
completed_atThe date and time the transaction was completed, mandatory for completed state only.ISO date/time
scheduled_forThe optional date the transaction was scheduled for.ISO date
referenceThe payment reference provided by the user.Text
related_transaction_idThe ID of the related transaction. Only applicable when type=refund.UUID
legsThe legs of a transaction. There are 2 legs between your Revolut accounts and 1 leg in other cases.Array of objects

Legs object

An object in the legs array contains the following fields:

FieldDescriptionFormat
leg_idThe ID of the leg.UUID
account_idThe ID of the account the transaction is associated with.UUID
counterpartyThe counterparty object.Object
amountThe transaction amount.Decimal
feeThe optional transaction fee amount.Decimal
currencyThe transaction currency.3-letter ISO currency code
bill_amountThe billing amount for cross-currency payments.Decimal
bill_currencyThe billing currency for cross-currency payments.3-letter ISO currency code
descriptionThe transaction leg purpose.Text
balanceThe total balance of the account the transaction is associated with (optional).Decimal

Counterparty object

A counterparty object contains the following fields:

FieldDescriptionFormat
idThe counterparty ID.UUID
account_idThe counterparty account ID.UUID
account_typeThe type of counterparty account: self, revolut, external.Text

Transaction state changed event

See the example of a TransactionStateChanged event:

{
"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"
}
}
    See the field details
FieldDescriptionFormat
eventThe event name.Text
timestampThe event time.ISO date/time
dataThe transaction data.Object

Data object for transaction state changed

The data object contains the following fields:

FieldDescriptionFormat
idThe ID of the updated transaction.UUID
request_idThe request ID provided by the client.UUID
old_stateThe previous state of the transaction.Text
new_stateThe new state of the transaction.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 for 3 more times, each with a 10-minute interval.

warning

Webhook notifications might occasionally be sent more than once. Make sure that your webhook endpoints are idempotent by validating 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:

  • 34.77.201.175
  • 34.77.253.1
  • 35.197.250.116
  • 35.197.227.215
  • 35.246.21.235
  • 34.89.70.170

Security

Each webhook notification contains the following headers:

  • Revolut-Request-Timestamp: Timestamp of the webhook event.

  • 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 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

Retrieve a 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 using a signing secret. It is generated on webhook creation, and it only changes on the secret's rotation.

Revolut uses the HMAC SHA-256 algorithm to sign its webhooks. To obtain the signing secret, make a request to the following endpoint:

curl -X GET https://b2b.revolut.com/api/2.0/webhooks/{webhook_id} \
-H "Authorization: Bearer <your access token>"

In the response, you get signing_secret, which you can later use to verify the payload signature:

{
"id": "12345678-1234-1234-1234-123456781234",
"url": "https://example.com/webhook",
"signing_secret": "wsk_P5NCcAhGWRwXuLdSNfFkPPQHAgL97BKj"
}

Verify the payload signature

Follow these steps to verify the signature for the webhook's request payload:

1. Prepare the payload to sign

To calculate the payload_to_sign string, concatenate the version, Revolut-Request-Timestamp, and the raw webhook payload without spaces, all separated by a full stop (.):

payload_to_sign = ${version}.${Revolut-Request-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"}
caution

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

Compute the hash-based message authentication code (HMAC) using the SHA256 hash function, the signing_secret for the webhook as the key and the signed_secret string as the message.

For example, you can use the following Python implementation as a reference:

import hmac
import hashlib

signing_secret = 'wsk_r59a4HfWVAKycbCaNO1RvgCJec02gRd8'

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

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.

tip

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
warning

We recommend that you test your implementation of webhook signature validation in the Sandbox environment before implementing it in production.

Rotate a webhook signing secret

You can rotate a webhook signing secret using the following request:

curl -X POST https://b2b.revolut.com/api/2.0/webhooks/{webhook_id}/rotate-signing-secret \
-H "Authorization: Bearer <your access token>"

As a response, you get a new signing_secret to use.

If you pass the optional expiration_period parameter, the old secret remains valid until the expiration period has passed. Otherwise, it is invalidated immediately.

This means that in the period when multiple signing secrets remain valid, multiple signatures 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).

Was this page helpful?