Validate the signature of your Webhook Endpoint

Verify that the events are being sent by Fintoc

Fintoc signs every event sent to your webhook endpoints with the Fintoc-Signature header. This header allows you to verify that every event received was in fact sent by Fintoc and not by a third party.

Fintoc generates a secret that is shown only once at the moment of registration of a Webhook Endpoint. This secret is necessary to validate the Fintoc signature of every event.

Validating the signature

Each event sent by Fintoc includes the Fintoc-Signature header. The header structure is the same for every event. It contains a timestamp denoted by the t key and a signature denote by the v1 key. Here is an example of a Fintoc-Signature header:

t=1620870928,v1=4df951e02db34a3f333bccad26d207993e9b14d78ac77cec026091991f567f6d

Signatures sent by Fintoc are just HMAC codes generated when signing the raw body of the request with the SHA-256 hash function at the moment indicated by the timestamp and using the Webhook Endpoint secret.

Let's begin validating the event's signature!

Extracting the timestamp and the header signature

Split the Fintoc-Signature header into an array by the , character. Then, split each element of the generated array by the = character to obtain a key-value pair. Finally, get the corresponding values for each key.

# using flask request
header_value = request.headers.get('Fintoc-Signature')
timestamp, event_signature = [x.split('=')[1] for x in header_value.split(',')]

Re-building the signed message

The message that Fintoc signed before sending the webhook corresponds to the timestamp concatenated to a . character and the the raw body of the request. Let's re-build that same message using the timestamp that we just untangled from the Fintoc-Signature header and the raw body of the request.

import json

# using flask request
message = f"{timestamp}.{request.get_data().decode('utf-8')}"

Make sure to use the raw request body directly from the request. Different libraries can represent the parsed JSON differently. An example of the value that message should have would be the following:

'1626102791.{"id":"evt_DyzYBwdC07ao5MqG","type":"link.credentials_changed","mode":"test","created_at":"2021-07-12T15:11:09.875Z","data":{"id":"link_00000000","mode":"test","active":true,"object":"link","status":"active","accounts":null,"username":"416148503","holder_id":"416148503","created_at":"2021-06-24T00:00:00.000Z","link_token":null,"holder_type":"individual","institution":{"id":"cl_banco_bbva","name":"Banco BBVA","country":"cl"}},"object":"event"}'

Generating the signature

Now that you have the message that needs to be signed, let's do just that, using the SHA-256 hash function and the secret generated when creating the webhook endpoint.

import hmac
from hashlib import sha256

encoded_secret = YOUR_WEBHOOK_SECRET.encode('utf-8')
encoded_message = message.encode('utf-8')
hmac_object = hmac.new(encoded_secret, msg=encoded_message, digestmod=sha256)
signature = hmac_object.hexdigest()

Comparing the signatures

Finally, let's compare the signature that we untangled from the Fintoc-Signature header to the one that we just obtained following the guide's steps. If both signatures match, then you can assume that the event was in fact sent by Fintoc.

import hmac

valid_signature = hmac.compare_digest(signature, event_signature)

Preventing a replay attack

To avoid a replay attack, we recommend that you define a tolerance range for event delays. When receiving and obtaining an event's timestamp, verify that it falls within your tolerance range. If that is not the case, discard the event.