Skip to main content

How to Verify Payload Origin for QuickAlerts

Updated on
Nov 22, 2023

Overview

When QuickAlerts sends an alert payload to your webhook URL, we include a variety of headers that you can use to verify that the payload came from QuickAlerts. For additional security, you can implement signature validation which is an optional way to verify the authenticity of payloads received by your webhook URL.

Headers We Send

  • x-qn-timestamp: The timestamp when this payload was sent.
  • x-qn-signature: The signature we generated for this payload.
  • x-qn-nonce: The nonce we used in the generation of the payload signature.
  • x-qn-content-hash: The hash generated by the content included in this payload.
  • x-qn-notificationid: The ID of the QuickAlerts expression that triggered this payload.

Locating the Webhook Security Token

  1. Sign in to the QuickNode developer portal and navigate to QuickAlerts.
  2. Click on the Destinations tab.
  3. View the details of the specific destination you want to verify.
  4. Within the destination details, find the Security Token field.

The Security Token is a value known only to you and QuickAlerts. It is used to generate a signature that can be validated on your end to ensure the authenticity of the received payloads.

To verify the authenticity of payloads, you can implement signature validation on your webhook server using the provided headers and your Webhook Security Token. This helps protect your application from receiving tampered or malicious payloads from unauthorized sources.

Verifying a Payload

Below are code snippets demonstrating how to verify the signature for different programming languages. These examples assume that the webhook headers (timestamp, nonce, and signature) are already available as variables. Replace the secret constant with your Webhook Security Token.

Javascript/TypeScript (using Node.js)

Javascript/TypeScript (Node.js) example
const crypto = require('crypto');

const secret = 'your_security_token';
const givenSign = req.headers['x-qn-signature'];
const nonce = req.headers['x-qn-nonce'];
const contentHash = req.headers['x-qn-content-hash'];
const timestamp = req.headers['x-qn-timestamp'];

const hmac = crypto.createHmac('sha256', secret);
hmac.update(nonce + contentHash + timestamp);

const expectedSign = hmac.digest('base64');

if (givenSign === expectedSign) {
console.log('The signature given matches the expected signature and is valid.');
} else {
console.log('The signature given does not match the expected signature and is invalid.');
}


Go

Go example
package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"strings"
)

func main() {
secret := "your_security_token"
givenSign := req.Header.Get("x-qn-signature")
nonce := req.Header.Get("x-qn-nonce")
contentHash := req.Header.Get("x-qn-content-hash")
timestamp := req.Header.Get("x-qn-timestamp")

h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(nonce + contentHash + timestamp))

expectedSign := base64.StdEncoding.EncodeToString(h.Sum(nil))

if strings.Compare(givenSign, expectedSign) == 0 {
fmt.Println("The signature given matches the expected signature and is valid.")
} else {
fmt.Println("The signature given does not match the expected signature and is invalid.")
}
}

PHP

PHP example
<?php

$secret = 'your_security_token';
$givenSign = $_SERVER['HTTP_X_QN_SIGNATURE'];
$nonce = $_SERVER['HTTP_X_QN_NONCE'];
$contentHash = $_SERVER['HTTP_X_QN_CONTENT_HASH'];
$timestamp = $_SERVER['HTTP_X_QN_TIMESTAMP'];

$hash = hash_hmac('sha256', $nonce . $contentHash . $timestamp, $secret, true);
$expectedSign = base64_encode($hash);

if ($givenSign === $expectedSign) {
echo 'The signature given matches the expected signature and is valid.';
} else {
echo 'The signature given does not match the expected signature and is invalid.';
}

?>

Python

Python example
import hmac
import hashlib
import base64

secret = 'your_security_token'
given_sign = request.headers.get('x-qn-signature')
nonce = request.headers.get('x-qn-nonce')
content_hash = request.headers.get('x-qn-content-hash')
timestamp = request.headers.get('x-qn-timestamp')

h = hmac.new(secret.encode(), digestmod=hashlib.sha256)
h.update((nonce + content_hash + timestamp).encode())

expected_sign = base64.b64encode(h.digest()).decode()

if given_sign == expected_sign:
print('The signature given matches the expected signature and is valid.')
else:
print('The signature given does not match the expected signature and is invalid.')

Remember to replace the placeholder values in the examples with the actual values from your webhook headers and security token.

Content hash verification

If you prefer to verify the payload signature by creating your own content hash using the body of the webhook payload, you can follow these steps:

  1. Retrieve the body of the webhook payload.
  2. Compute the SHA-256 hash of the webhook url path and payload body.
  3. Hash the nonce, computed hash, and timestamp, then encode the hash value in Base64 format.
  4. Compare the resulting signature with the x-qn-signature header value included in the payload headers.

Below is some sample Python code demonstrating how to compute a content hash using the webhook url path and payload body, then generating the payload signature. This task can be achieved with other programming languages as well.

Python example
import hmac
import hashlib
import base64
from urllib.parse import urlparse

secret = 'your_security_token'
given_sign = request.headers.get('x-qn-signature')
nonce = request.headers.get('x-qn-nonce')
timestamp = request.headers.get('x-qn-timestamp')
webhook_url = 'your_webhook_url'

# Retrieve the payload body
payload_body = request.get_data()

# Parse the URL and extract the path
parsed_url = urlparse(webhook_url)
path = parsed_url.path

# Combine the path with the payload body
data = path + payload_body

# Convert the combined string to bytes
data_bytes = data.encode()

# Compute the SHA-256 hash of the data and convert it to hex
computed_hash = hashlib.sha256(data_bytes).hexdigest()

# Generate the expected signature
signature_data = nonce.encode() + computed_hash.encode() + timestamp.encode()
expected_sign = base64.b64encode(hmac.new(secret.encode(), signature_data, hashlib.sha256).digest()).decode()

# Compare the given signature with the expected signature
if given_sign == expected_sign:
print('The signature given matches the expected signature and is valid.')
else:
print('The signature given does not match the expected signature and is invalid.')

Share this doc