Skip to main content
Securing the flow of data between two endpoints is crucial to prevent unauthorized access, data breaches, and malicious attacks. It ensures data integrity, confidentiality, and authenticity, protecting sensitive information from being intercepted or tampered with. In Helium Id’s context, it is important to secure the data flow between our systems and your webhook listeners. Helium Id uses HMAC signatures sent via the Webhook-Signature header for that purpose.

What must be signed?

HMAC signatures are a security measure generated by Helium Id for your webhook listeners to ensure the exchanged data has not been tampered with. When validating a signature on your end, you must recreate the signature using your API Key (acting as the secret) and compare it against the Webhook-Signature header provided in the webhook request.

The Signature Payload

To generate the HMAC signature correctly, you must construct a specific signature string. The payload to sign is a concatenation of the current timestamp and the raw JSON payload body, separated by a period (.). Format:
{timestamp}.{payload}
Where:
  • timestamp is the UNIX epoch time (in milliseconds) extracted directly from the Webhook-Timestamp header sent by Helium Id.
  • payload is the exact JSON stringified body of the webhook, containing the event and data properties.

Prerequisites

If you want to validate a signature for a webhook request, have the following information at hand:
  1. API Key - Use your Helium Id API Key as the secret key to sign the payload.
  2. Webhook Payload - The raw JSON body of the webhook.
  3. Webhook Headers - The Webhook-Timestamp and Webhook-Signature headers provided in the request.

Generate and Validate an HMAC Signature

Below are examples showing how to generate an HMAC-SHA256 hash to validate the Webhook-Signature sent by Helium Id.

JavaScript (Node.js) Example

Here is an example in JavaScript of how you can generate the HMAC-SHA256 hash using the built-in crypto module.
const crypto = require("crypto");

// 1. Define your API Key (acting as the secret)
const secret = "YOUR_HELIUM_ID_API_KEY";

// 2. Define the event and payload data
const eventName = "verification.successful";
const eventPayload = {
  id: "69d830f88c8373aa37c95974",
  status: "successful",
};

// 3. Stringify the payload exactly as received
const payload = JSON.stringify({
  event: eventName,
  data: eventPayload,
});

// 4. Extract the timestamp from the webhook headers
const timestamp = req.headers["webhook-timestamp"];

// 5. Construct the signature payload
const signaturePayload = `${timestamp}.${payload}`;

// 6. Generate the HMAC hash
const signature = crypto
  .createHmac("sha256", secret)
  .update(signaturePayload)
  .digest("hex");

console.log("Generated Signature:", signature);

// 7. Validate the signature
const incomingSignature = req.headers["webhook-signature"];

if (
  crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(incomingSignature))
) {
  console.log("Valid Webhook Payload!");
} else {
  console.error("Invalid Signature. Rejecting request.");
}

Python Example

Here is how you can perform the same validation in Python using the built-in hmac and hashlib libraries.
import hmac
import hashlib
import json

# 1. Define your API Key
secret = "YOUR_HELIUM_ID_API_KEY".encode('utf-8')

# 2. Define the payload
event_name = "verification.successful"
event_payload = {
    "id": "69d830f88c8373aa37c95974",
    "status": "successful"
}

payload_dict = {
    "event": event_name,
    "data": event_payload
}

# 3. Stringify the payload (ensure no spaces after separators to match typical JSON stringify)
payload_str = json.dumps(payload_dict, separators=(',', ':'))

# 4. Extract the timestamp from the webhook headers
# Assuming `request` is your framework's request object
timestamp = request.headers.get("Webhook-Timestamp")

# 5. Construct the signature payload
signature_payload = f"{timestamp}.{payload_str}".encode('utf-8')

# 6. Generate the HMAC hash
signature = hmac.new(secret, signature_payload, hashlib.sha256).hexdigest()

print("Generated Signature:", signature)

# 7. Validate the signature
incoming_signature = request.headers.get("Webhook-Signature")

if hmac.compare_digest(signature, incoming_signature):
    print("Valid Webhook Payload!")
else:
    print("Invalid Signature. Rejecting request.")

Go Example

Here is how you can perform the validation in Go using the crypto/hmac and crypto/sha256 packages.
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"encoding/json"
	"fmt"
)

func main() {
	// 1. Define your API Key
	secret := []byte("YOUR_HELIUM_ID_API_KEY")

	// 2. Define the payload
	payloadDict := map[string]interface{}{
		"event": "verification.successful",
		"data": map[string]string{
			"id":     "69d830f88c8373aa37c95974",
			"status": "successful",
		},
	}

	// 3. Stringify the payload
	payloadBytes, _ := json.Marshal(payloadDict)
	payloadStr := string(payloadBytes)

	// 4. Extract the timestamp from the webhook headers
	// Assuming `req` is your *http.Request object
	timestamp := req.Header.Get("Webhook-Timestamp")

	// 5. Construct the signature payload
	signaturePayload := fmt.Sprintf("%s.%s", timestamp, payloadStr)

	// 6. Generate the HMAC hash
	h := hmac.New(sha256.New, secret)
	h.Write([]byte(signaturePayload))
	signature := hex.EncodeToString(h.Sum(nil))

	fmt.Println("Generated Signature:", signature)

	// 7. Validate the signature
	incomingSignature := req.Header.Get("Webhook-Signature")

	if subtle.ConstantTimeCompare([]byte(signature), []byte(incomingSignature)) == 1 {
		fmt.Println("Valid Webhook Payload!")
	} else {
		fmt.Println("Invalid Signature. Rejecting request.")
	}
}

Best Practices

  • Do not expose your API Key: Your API Key is sensitive. Never hardcode it directly into client-side applications. Always store it securely in environment variables.
  • Timing Attacks: When comparing the generated signature with the received x-hmac-signature header, use a constant-time string comparison function (like crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) to prevent timing attacks.
  • Timestamp Verification: You can implement a tolerance window (e.g., 5 minutes) by comparing the timestamp in the payload against your server’s current time to protect against replay attacks.