Headers
| Header | Description |
|---|---|
X-Finogate-Signature | sha256= followed by the hex HMAC-SHA256 digest. |
X-Finogate-Timestamp | Unix time (seconds) when the request was signed. |
X-Finogate-Event-Id | The event id. Identical on every retry. |
X-Finogate-Delivery-Id | This endpoint’s delivery of the event. |
X-Finogate-Signature: sha256=5f0c2d7f0c0e9b4a8e1d3c6b9a7f2e4d1c8b5a3f6e9d2c7b4a1f8e5d3c6b9a7f
X-Finogate-Timestamp: 1760346922
X-Finogate-Event-Id: 6f1d2c8e-4b7a-4e0f-9a51-2c3d4e5f6a7b
X-Finogate-Delivery-Id: 0e1f2a3b-4c5d-4e6f-8a7b-9c0d1e2f3a4b
Verifying a Request
- Read the raw request body. Don’t parse and re-serialize it.
- Build the signed message:
{X-Finogate-Timestamp}.{raw_body}. - Compute HMAC-SHA256 of the message with your secret, hex-encoded, and prefix it with
sha256=. - Compare it with
X-Finogate-Signatureusing a constant-time comparison. - Reject the request if the timestamp is more than 5 minutes from your current time.
Examples
const crypto = require("crypto");
function verifyFinogatesWebhook(rawBody, headers, secret) {
const timestamp = headers["x-finogate-timestamp"];
const signature = headers["x-finogate-signature"];
if (!timestamp || !signature) throw new Error("Missing signature headers");
if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) {
throw new Error("Timestamp outside allowed window");
}
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
throw new Error("Invalid signature");
}
}
import hashlib
import hmac
import time
def verify_finogates_webhook(raw_body: bytes, headers: dict, secret: str) -> None:
timestamp = headers["X-Finogate-Timestamp"]
signature = headers["X-Finogate-Signature"]
if abs(int(time.time()) - int(timestamp)) > 300:
raise ValueError("Timestamp outside allowed window")
message = f"{timestamp}.".encode() + raw_body
expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError("Invalid signature")
package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"strconv"
"time"
)
func VerifyFinogatesWebhook(rawBody []byte, timestamp, signature, secret string) error {
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return errors.New("invalid timestamp")
}
if d := time.Now().Unix() - ts; d > 300 || d < -300 {
return errors.New("timestamp outside allowed window")
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp + "."))
mac.Write(rawBody)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
return errors.New("invalid signature")
}
return nil
}
<?php
function verifyFinogatesWebhook(string $rawBody, string $timestamp, string $signature, string $secret): void
{
if (abs(time() - (int) $timestamp) > 300) {
throw new Exception("Timestamp outside allowed window");
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
throw new Exception("Invalid signature");
}
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
public final class WebhookVerifier {
public static void verify(String rawBody, String timestamp, String signature, String secret) throws Exception {
if (Math.abs(System.currentTimeMillis() / 1000 - Long.parseLong(timestamp)) > 300) {
throw new SecurityException("Timestamp outside allowed window");
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal((timestamp + "." + rawBody).getBytes(StandardCharsets.UTF_8));
String expected = "sha256=" + HexFormat.of().formatHex(digest);
if (!MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8))) {
throw new SecurityException("Invalid signature");
}
}
}
require "openssl"
def verify_finogates_webhook(raw_body, timestamp, signature, secret)
raise "Timestamp outside allowed window" if (Time.now.to_i - timestamp.to_i).abs > 300
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{raw_body}")
raise "Invalid signature" unless OpenSSL.fixed_length_secure_compare(expected, signature)
end
OpenSSL.fixed_length_secure_compare raises when the lengths differ; treat that as an invalid signature.
