Skip to main content

How to Validate Incoming Streams Webhook Messages

Updated on
Jan 02, 2025

10 min read

Streams

Streams is available starting from the Free 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


  • Implement webhook signature verification in Node.js, Python, or Go
  • Set up a server to handle incoming webhook messages
  • Verify signatures using HMAC SHA-256
  • Process both compressed and uncompressed payloads
  • Test your verification implementation
  • Discuss best security practices to enhance the safety of your verification process

What You Will Need


  • Your preferred language environment (Node.js, Python, or Go)
  • A code editor (e.g., VSCode)
  • ngrok installed
  • Your Stream's security token from the QuickNode dashboard
  • Language-specific requirements (see dependencies table below)
DependencyVersion
node.js>=16.x
express^4.18.2
body-parser^1.20.2
ngrok^3.0.0

Understanding Webhook Signatures

When your server receives a webhook from Streams, it includes three critical headers that are used for verification:


  • X-QN-Nonce: A unique string that prevents replay attacks
  • X-QN-Signature: The HMAC signature you need to verify
  • X-QN-Timestamp: The timestamp when the message was signed

The signature is created by combining these elements with the payload in a specific way, then applying an HMAC-SHA256 hash using your Stream's security token as the key. Now, we'll move onto the coding part of the guide and show you how to validate incoming webhook messages from Streams.

Stream Setup and Security Token

First, we'll need a security token for signature verification. You can either:


  • If you have an existing Stream: Go to your Stream's Settings tab and keep your security token handy (we'll come back to this later)
  • If you need to create a Stream:
    1. Visit TypedWebhook.tools and copy the provided webhook URL
    2. Go to the Streams section on your QuickNode dashboard
    3. Click Create Stream
    4. Configure your Stream:
      • Select Ethereum as the blockchain and Mainnet as your network
      • Choose Blocks as your dataset
      • Paste the TypedWebhook.tools URL in the webhook destination settings
    5. Create the Stream and copy your security token via the Settings tab

Note: This TypedWebhook.tools URL is temporary until we show you later on in the guide how to run your own local server and ngrok to validate the incoming message.

Setting Up Your Development Environment

First, create a new project directory and set up your environment based on your chosen language:


mkdir webhook-verification
cd webhook-verification
npm init -y
npm install express body-parser

Implementing the Verification Server

Now, let's implement the verification server. We'll create a server that:


  • Listens for POST requests on a /webhook endpoint
  • Extracts the necessary headers (e.g., nonce, signature, timestamp)
  • Verifies the signature
  • Returns HTTP responses

Choose your preferred language and create your server file within the appropriate directory (based on the language you chose). Remember to replace the your_security_token_here placeholder string with your actual security token.


const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
const PORT = 9999;

app.use(bodyParser.raw({
type: '*/*',
limit: '50mb'
}));

app.use(bodyParser.json());

function verifySignature(secretKey, payload, nonce, timestamp, givenSignature) {
// First concatenate as strings
const signatureData = nonce + timestamp + payload;

// Convert to bytes
const signatureBytes = Buffer.from(signatureData);

// Create HMAC with secret key converted to bytes
const hmac = crypto.createHmac('sha256', Buffer.from(secretKey));
hmac.update(signatureBytes);
const computedSignature = hmac.digest('hex');

console.log('\nSignature Debug:');
console.log('Message components:');
console.log('- Nonce:', nonce);
console.log('- Timestamp:', timestamp);
console.log('- Payload first 100 chars:', payload.substring(0, 100));
console.log('\nSignatures:');
console.log('- Computed:', computedSignature);
console.log('- Given:', givenSignature);

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

app.post('/webhook', async (req, res) => {
const secretKey = 'your_security_token_here';
const nonce = req.headers['x-qn-nonce'];
const timestamp = req.headers['x-qn-timestamp'];
const givenSignature = req.headers['x-qn-signature'];

if (!nonce || !timestamp || !givenSignature) {
console.error('Missing required headers');
return res.status(400).send('Missing required headers');
}

try {
const payloadString = req.body.toString('utf8');
const isValid = verifySignature(
secretKey,
payloadString,
nonce,
timestamp,
givenSignature
);

if (isValid) {
console.log('\n✅ Signature verified successfully');
return res.status(200).send('Webhook received and verified');
} else {
console.log('\n❌ Signature verification failed');
return res.status(401).send('Invalid signature');
}
} catch (error) {
console.error('Error processing webhook:', error);
return res.status(500).send('Error processing webhook');
}
});

app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

Starting Your Server

Start your verification server based on your implementation:


node server.js

Setting Up ngrok

Then, create a tunnel to your local server (adjust the port based on your implementation):


ngrok http 9999

Keep the terminal window handy as we'll need the URL when updating our Stream's webhook destination.

Validating Messages with Incoming Stream Data


  1. First, copy the ngrok URL running in your terminal (e.g., https://abc123.ngrok.io)
  2. Then, go to your Stream's settings in the QuickNode dashboard.
  3. Pause your Stream and update the webhook URL to your ngrok URL + /webhook (e.g., https://abc123.ngrok.io/webhook)
  4. Resume your Stream
  5. Watch your server logs. For each block, you should see output similar to:
Signature Debug:
Message components:
- Nonce: 02c6d2644296c8b830970891410825a3
- Timestamp: 1735613672
- Payload first 100 chars: {"data":[{"baseFeePerGas":"0xd557bf66","blobGasUsed":"0x60000","difficulty":"0x0","excessBlobGas":"0

Signatures:
- Computed: 049827ff010a1c24cd21594d72e490fd43a48d9e69a5c18628788063665134cc
- Given: 049827ff010a1c24cd21594d72e490fd43a48d9e69a5c18628788063665134cc

✅ Signature verified successfully

If you see successful verification messages, congrats! Your server is properly verifying webhook signatures.

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