Skip to main content

How to Validate Incoming Streams Webhook Messages

Updated on
Sep 4, 2024

5 min read

Streams Beta

Streams Beta is available starting from the Build plan. For teams with unique requirements, we offer tailored datasets, dedicated support, and custom integrations. Contact our team for more information.

Overview

Streaming blockchain data in real-time can often be daunting due to the complexity of infrastructure setup and the ongoing management required. QuickNode addresses this challenge with Streams, a blockchain data streaming solution that allows you to easily stream both historical and real-time data directly to your applications or services.

This guide will take you through the process of verifying HMAC signatures for incoming messages from Streams webhook services. Ensuring the integrity and authenticity of these messages is crucial for securing your applications against tampering and forgery. By the end of this guide, you'll be equipped with the knowledge to implement signature verification using Python effectively.

What You Will Do


  • Retrieve necessary headers (nonce, signature, timestamp) from incoming HTTP requests
  • Prepare the raw JSON payload for signature verification
  • Write and execute a Python script to verify the HMAC signature
  • Discuss best security practices to enhance the safety of your verification process

What You Will Need


  • A Stream created on QuickNode account
  • A code editor (e.g., VSCode)
  • Python installed on your system
  • Basic familiarity with handling HTTP requests in your server environment
  • Access to the secret key used for HMAC signatures, found in your webhook stream settings
Looking to Create a Stream?

If you haven't created any Streams yet and want to start streaming blockchain data effortlessly, check out our comprehensive guide. This guide will walk you through the simple steps to set up your Streams with QuickNode.

Verify HMAC Signatures

Retrieving Necessary Headers

Each message from our webhook service includes three critical headers that are explained below. Extracting these values from the headers of incoming HTTP requests is the first step in the verification process.


  • X-QN-Nonce: A unique string that is used once per signature to prevent replay attacks. (e.g., 216820ba6d45d2271eb80a0afe957cc7)
  • X-QN-Signature: The HMAC signature you need to verify. (e.g., 39eb54760585319d29b645da8d67590b28287536daa0a7da8d9472966a2d58d5)
  • X-QN-Timestamp: The timestamp when the message was signed, which you can use to prevent replay attacks. (e.g., 1713367463)

Preparing the Payload

When verifying the HMAC signature, it's crucial to use the uncompressed payload data. Even if the webhook message is received in a compressed format (e.g., gzip), you must first decompress it before using it for signature verification. This is because the signature was generated using the uncompressed data on the server side.

Getting a Security Token

Navigate to the Streams section in the QuickNode Dashboard, and locate the security token in the Settings section. Make sure to copy this token, as you will need it for the steps that follow.

Verifying the HMAC Signature

We now have all the information to verify the HMAC signature. Now, create a Python file (i.e., verify-signature.py) and open it with your code editor. Then, add the following code to the file.

After these steps, replace your_nonce_here with the nonce, your_given_signature_here with the signature, your_timestamp_here with the timestamp from your headers, and your_secret_key_here with the actual secret key provided in your Stream settings, as detailed in the previous section. Ensure these values are precisely copied to maintain the integrity of the verification process.

In each of these examples below:

  • We first decompress the received payload (assuming it's compressed with gzip).
  • We then use the uncompressed payload for signature verification.
  • The verifySignature function receives the uncompressed payload along with other pieces of data required to verify the signature.

NodeJS example

const crypto = require('crypto');
const zlib = require('zlib');

function verifySignature(secretKey, uncompressedPayload, nonce, timestamp, givenSignature) {
const message = nonce + timestamp + uncompressedPayload;
const computedSignature = crypto
.createHmac('sha256', secretKey)
.update(message)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(computedSignature, 'hex'),
Buffer.from(givenSignature, 'hex')
);
}

// Example usage
const secretKey = 'your_security_token_here';
const compressedPayload = /* Your received compressed payload */;
const nonce = 'your_nonce_here';
const timestamp = 'your_timestamp_here';
const givenSignature = 'your_given_signature_here';

zlib.gunzip(compressedPayload, (err, uncompressedPayload) => {
if (err) {
console.error('Error decompressing payload:', err);
return;
}

try {
const isValid = verifySignature(secretKey, uncompressedPayload.toString(), nonce, timestamp, givenSignature);
console.log('Signature is valid:', isValid);
} catch (error) {
console.error('Error verifying signature:', error);
}
});

Python example

import hmac
import hashlib
import gzip

def verify_signature(secret_key, uncompressed_payload, nonce, timestamp, given_signature):
message = nonce + timestamp + uncompressed_payload
computed_signature = hmac.new(
secret_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(computed_signature, given_signature)

# Example usage
secret_key = 'your_security_token_here'
compressed_payload = b'your_compressed_payload_here' # Received as bytes
nonce = 'your_nonce_here'
timestamp = 'your_timestamp_here'
given_signature = 'your_given_signature_here'

try:
# Decompress the payload
uncompressed_payload = gzip.decompress(compressed_payload).decode('utf-8')

is_valid = verify_signature(secret_key, uncompressed_payload, nonce, timestamp, given_signature)
print('Signature is valid:', is_valid)
except Exception as e:
print('Error verifying signature:', str(e))

Go example

package main

import (
"bytes"
"compress/gzip"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
)

func verifySignature(secretKey, uncompressedPayload, nonce, timestamp, givenSignature string) (bool, error) {
message := nonce + timestamp + uncompressedPayload
h := hmac.New(sha256.New, []byte(secretKey))
_, err := h.Write([]byte(message))
if err != nil {
return false, err
}
computedSignature := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(computedSignature), []byte(givenSignature)), nil
}

func main() {
secretKey := "your_security_token_here"
compressedPayload := []byte("your_compressed_payload_here")
nonce := "your_nonce_here"
timestamp := "your_timestamp_here"
givenSignature := "your_given_signature_here"

// Decompress the payload
reader, err := gzip.NewReader(bytes.NewReader(compressedPayload))
if err != nil {
fmt.Println("Error creating gzip reader:", err)
return
}
defer reader.Close()

uncompressedPayload, err := ioutil.ReadAll(reader)
if err != nil {
fmt.Println("Error decompressing payload:", err)
return
}

isValid, err := verifySignature(secretKey, string(uncompressedPayload), nonce, timestamp, givenSignature)
if err != nil {
fmt.Println("Error verifying signature:", err)
return
}
fmt.Println("Signature is valid:", isValid)
}

Best Security Practices

While implementing the HMAC signature verification, ensure the following practices to secure your application:


  • Always keep the security token confidential and securely stored, not hardcoded in publicly accessible areas of your code.
  • Log all validation attempts, both successful and failed, to aid in auditing and troubleshooting.
  • Implement additional checks like timestamp validation to prevent replay attacks.

Conclusion

Congratulations! You've successfully learned how to validate incoming Streams webhook messages by verifying HMAC signatures. This practice is vital for protecting your applications from external threats and ensuring data integrity.

If you have questions, please contact us directly. If you have any ideas or suggestions, such as new destinations, features, metrics, or datasets, you want us to support.

Also, stay up to date with the latest by following us on Twitter and joining our Discord and Telegram announcement channel.

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide