Check signatures
Blockfrost signs the webhook events it sends to your endpoints. The signature is included in a request Blockfrost-Signature
header. This allows you to verify that the events were sent by Blockfrost, not by a third party. You can verify signatures either using our official SDKs, or manually using your own solution.
Before you can verify signatures, you need to retrieve your webhook authentication token from your Secure Webhook settings.
Using SDK
The fastest way to verify the webhook signature is to use built-in helper function from Blockfrost SDKs.
- Javascript (Node.js)
- Python
- Go
const express = require("express");
const blockfrost = require("@blockfrost/blockfrost-js");
const { verifyWebhookSignature, SignatureVerificationError } = blockfrost;
// You will find your webhook secret auth token in your webhook settings in the Blockfrost Dashboard
const SECRET_AUTH_TOKEN = 'WEBHOOK-AUTH-TOKEN';
const app = express();
app.post('/webhook', express.json({ type: "application/json" }), (request, response) => {
// Validate the webhook signature
const signatureHeader = request.headers["blockfrost-signature"];
try {
const event = verifyWebhookSignature(
JSON.stringify(request.body), // Stringified request.body (Note: In AWS Lambda you don't need to call JSON.stringify as event.body is already stringified)
signatureHeader,
SECRET_AUTH_TOKEN,
600 // Optional param to customize maximum allowed age of the webhook event, defaults to 600s
);
// Signature is valid, process the event
} catch (error) {
// In case of invalid signature verifyWebhookSignature will throw SignatureVerificationError
// for easier debugging you can access passed signatureHeader and webhookPayload values (error.detail.signatureHeader, error.detail.webhookPayload)
console.error(error);
return response.status(400).send("Signature is not valid!");
}
}
app.listen(6666, () => console.log("Running on port 6666"));
from flask import Flask, request, json
from blockfrost import verify_webhook_signature
# You will find your webhook secret auth token in your webhook settings in the Blockfrost Dashboard
SECRET_AUTH_TOKEN = "SECRET-WEBHOOK-AUTH-TOKEN"
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
# Validate the webhook signature
request_bytes = request.get_data()
try:
verify_webhook_signature(
request_bytes, request.headers['Blockfrost-Signature'], SECRET_AUTH_TOKEN)
except SignatureVerificationError as e:
# In case of invalid signature verify_webhook_signature will raise SignatureVerificationError
# for easier debugging you can access passed header and request_body values (e.header, e.request_body)
print('Webhook signature is invalid.', e)
return 'Invalid signature', 403
# Signature is valid, process the event
if __name__ == "__main__":
app.run(host='0.0.0.0', port=6666)
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/blockfrost/blockfrost-go"
)
const SECRET_AUTH_TOKEN string = "SECRET-WEBHOOK-AUTH-TOKEN"
func main() {
http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) {
const MaxBodyBytes = int64(524288)
req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes)
payload, err := io.ReadAll(req.Body)
fmt.Printf("Received webhook request.\n")
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
event, err := blockfrost.VerifyWebhookSignature(payload, req.Header.Get("Blockfrost-Signature"), SECRET_AUTH_TOKEN)
if err != nil {
fmt.Fprintf(os.Stderr, "Error verifying webhook signature: %v\n", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// Signature is valid, process the event
w.WriteHeader(http.StatusOK)
})
fmt.Println("Server is starting on port 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Server failed to start: %v\n", err)
}
}
Verifying the signature manually
Blockfrost-Signature
header contains an unix timestamp with the time of firing the request (prefixed by t=
) and a signature (prefixed by schema version, currently v1=
).
Signature itself (part after v1=
) is created by concatenating:
- The unix timestamp (as a string)
- The character
.
- The request JSON payload
Then HMAC with the SHA256 hash function is computed. The concatenated string is used as a message.
Blockfrost-Signature
header example:
t=1648550558,v1=162381a59040c97d9b323cdfec02facdfce0968490ec1732f5d938334c1eed4e
Although it is recommended to use our SDKs to verify webhook event signatures, you can create a custom solution by following these steps.
1. Parse the timestamp and signature from the header
Split the header, using the ,
character as the separator, to get a list of elements. Then split each element, using the =
character as the separator, to get a key-value pair.
The value for the key t
corresponds to the timestamp, and the value after v1
corresponds to the signature. The header may contain multiple signatures with the same version schema.
2. Prepare the signature_payload
Prepare the signature_payload
by concatenating:
- The unix timestamp (as a string)
- The character
.
- The request JSON payload
3. Compute the expected signature
Compute an HMAC with the SHA256 hash function. Use the webhook authentication token as the key, and use the prepared signature_payload
string as the message.
4. Compare the signatures
Compare the computed signature with signatures extracted from the Blockfrost-Signature
.
If one of the signatures extracted from Blockfrost-Signature
matches the computed signature, then compute the difference between the current timestamp and the received timestamp and decide if the difference is within your tolerance.
By default, the Blockfrost SDK considers the signature valid if the difference is less than 10 minutes (600 seconds).
If none of the signatures in Blockfrost-Signature
header matches the computed signature, or if the difference between the timestamps is larger than the tolerance, the signature is considered invalid.