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.
Updated almost 3 years ago