Verifying Webhook Requests
To ensure the authenticity and integrity of the webhook requests you receive from our service, we include an HMAC signature and timestamp in the request headers. Here's how you can verify the requests in your preferred programming language:
Extract the
X-Signature
andX-Timestamp
headers from the incoming request.Prepare the payload by:
If the webhook indicates additional data, concatenating the additionalData and
timestamp
separated by a dot (.
),If the webhook does not indicate any additional data, the
timestamp
itself can be used as the payload
Generate an HMAC signature using the SHA256 algorithm and the shared secret key.
Compare the generated signature with the received
X-Signature
. If they match, the request is authentic.Check if the
timestamp
is within an acceptable time window (e.g., 5 minutes) to prevent replay attacks.
Here are code samples for verifying webhook requests in different programming languages:
JS
const crypto = require('crypto');
const webhookSecret = "your-shared-secret";
const verifyOrderWebhook = (req, res, next) => {
const signature = req.headers['x-signature'];
const timestamp = parseInt(req.headers['x-timestamp'], 10);
const orderId = req.body.orderId;
const currentTimestamp = Math.floor(Date.now() / 1000);
if (Math.abs(currentTimestamp - timestamp) > 300) {
return res.status(401).json({ error: 'Webhook request is too old' });
}
const payload = orderId + '.' + timestamp;
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(payload);
const computedSignature = hmac.digest('hex');
if (computedSignature !== signature) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
};
const verifyWebhook = (req, res, next) => {
const signature = req.headers['x-signature'];
const timestamp = parseInt(req.headers['x-timestamp'], 10);
const currentTimestamp = Math.floor(Date.now() / 1000);
if (Math.abs(currentTimestamp - timestamp) > 300) {
return res.status(401).json({ error: 'Webhook request is too old' });
}
const payload = timestamp;
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(payload);
const computedSignature = hmac.digest('hex');
if (computedSignature !== signature) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
};
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class WebhookVerifier {
private static final String WEBHOOK_SECRET = "your-shared-secret";
public static boolean verifyOrderWebhook(String signature, long timestamp, String orderId) {
long currentTimestamp = System.currentTimeMillis() / 1000;
if (Math.abs(currentTimestamp - timestamp) > 300) {
return false;
}
String payload = orderId + "." + timestamp;
String computedSignature = generateSignature(payload);
return computedSignature.equals(signature);
}
public static boolean verifyWebhook(String signature, long timestamp) {
long currentTimestamp = System.currentTimeMillis() / 1000;
if (Math.abs(currentTimestamp - timestamp) > 300) {
return false;
}
String payload = timestamp;
String computedSignature = generateSignature(payload);
return computedSignature.equals(signature);
}
private static String generateSignature(String payload) {
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(WEBHOOK_SECRET.getBytes(), "HmacSHA256");
sha256_HMAC.init(secretKey);
byte[] hash = sha256_HMAC.doFinal(payload.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Error generating signature", e);
}
}
}
PHP
<?php
$webhookSecret = "your-shared-secret";
function verifyOrderWebhook($signature, $timestamp, $orderId) {
$currentTimestamp = time();
if (abs($currentTimestamp - $timestamp) > 300) {
return false;
}
$payload = $orderId . '.' . $timestamp;
$computedSignature = hash_hmac('sha256', $payload, $webhookSecret);
return hash_equals($computedSignature, $signature);
}
function verifyWebhook($signature, $timestamp) {
$currentTimestamp = time();
if (abs($currentTimestamp - $timestamp) > 300) {
return false;
}
$payload = $timestamp;
$computedSignature = hash_hmac('sha256', $payload, $webhookSecret);
return hash_equals($computedSignature, $signature);
}
// Usage, order webhook
$signature = $_SERVER['HTTP_X_SIGNATURE'];
$timestamp = intval($_SERVER['HTTP_X_TIMESTAMP']);
$orderId = $_POST['orderId'];
if (verifyOrderWebhook($signature, $timestamp, $orderId)) {
// Handle the webhook request
} else {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
}
C#
using System;
using System.Security.Cryptography;
using System.Text;
public class WebhookVerifier
{
private const string WebhookSecret = "your-shared-secret";
public static bool VerifyOrderWebhook(string signature, long timestamp, string orderId)
{
long currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(currentTimestamp - timestamp) > 300)
{
return false;
}
string payload = orderId + "." + timestamp;
string computedSignature = GenerateSignature(payload);
return computedSignature == signature;
}
public static bool VerifyWebhook(string signature, long timestamp)
{
long currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(currentTimestamp - timestamp) > 300)
{
return false;
}
string payload = timestamp;
string computedSignature = GenerateSignature(payload);
return computedSignature == signature;
}
private static string GenerateSignature(string payload)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(WebhookSecret)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
)
const webhookSecret = "your-shared-secret"
func verifyOrderWebhook(signature string, timestamp int64, orderId string) bool {
currentTimestamp := time.Now().Unix()
if abs(currentTimestamp-timestamp) > 300 {
return false
}
payload := orderId + "." + strconv.FormatInt(timestamp, 10)
computedSignature := generateSignature(payload)
return computedSignature == signature
}
func verifyWebhook(signature string, timestamp int64, orderId string) bool {
currentTimestamp := time.Now().Unix()
if abs(currentTimestamp-timestamp) > 300 {
return false
}
payload := strconv.FormatInt(timestamp, 10)
computedSignature := generateSignature(payload)
return computedSignature == signature
}
func generateSignature(payload string) string {
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write([]byte(payload))
return hex.EncodeToString(mac.Sum(nil))
}
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
func main() {
signature := "received-signature"
timestamp := int64(1623456789)
orderId := "order-123"
if verifyOrderWebhook(signature, timestamp, orderId) {
fmt.Println("Webhook verified")
} else {
fmt.Println("Invalid webhook")
}
}
Last updated