Verificarea semnăturii pentru Webhook

Această secțiune oferă exemple de implementare pentru verificarea autenticității cererilor primite, folosind o cheie de semnătură. Acest mecanism contribuie la securitatea integrării (ex. webhook-uri), asigurând că datele nu au fost modificate.

Semnătura este, de obicei, inclusă în header-ul cererii și trebuie validată prin calcularea unui hash HMAC cu cheia secretă partajată.

.NET

using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;

var jsonMessage = "[CALLBACK MESSAGE]";

var signatureKey = "[YOUR SIGNATURE KEY]";

var jsonNode = JsonNode.Parse(jsonMessage);

var resultElement = jsonNode!["result"]!;

var keys = new Dictionary<string, string>();

foreach (var property in (JsonObject)resultElement)
{
    var valueNode = property.Value;

    if (valueNode is null)
        continue;

    string valueStr = property.Key is "amount" or "commission"
        ? valueNode.GetValue<decimal>().ToString("F2")
        : valueNode.ToString();

    if (!string.IsNullOrWhiteSpace(valueStr))
        keys.Add(property.Key, valueStr);
}

var orderedKeys = keys.OrderBy(kv => kv.Key, StringComparer.OrdinalIgnoreCase).ToArray();

var additionalString =
    string.Join(":", orderedKeys.Select(kv => kv.Value));

var hash = SHA256.HashData(Encoding.UTF8.GetBytes($"{additionalString}:{signatureKey}"));

var result = Convert.ToBase64String(hash);

if (result == jsonNode["signature"]!.ToString())
    Console.WriteLine("Signature is valid");
else
    Console.WriteLine("INVALID SIGNATURE!");

.PHP

<?php

$jsonMessage = '[CALLBACK MESSAGE]';
$signatureKey = '[YOUR SIGNATURE KEY]';

$jsonNode = json_decode($jsonMessage, true);

$resultElement = $jsonNode['result'] ?? [];
$expectedSignature = $jsonNode['signature'] ?? '';

$keys = [];

foreach ($resultElement as $key => $value) {
    if (is_null($value)) {
        continue;
    }

    // Format "amount" and "commission" with 2 decimal places
    if ($key === 'amount' || $key === 'commission') {
        $valueStr = number_format((float)$value, 2, '.', '');
    } else {
        $valueStr = (string)$value;
    }

    if (trim($valueStr) !== '') {
        $keys[$key] = $valueStr;
    }
}

// Sort keys by key name (case-insensitive)
uksort($keys, 'strcasecmp');

// Build the string to hash
$additionalString = implode(':', $keys);
$hashInput = $additionalString . ':' . $signatureKey;

// Generate SHA256 hash and base64-encode it
$hash = hash('sha256', $hashInput, true);
$result = base64_encode($hash);

// Compare the result with the signature
if ($result === $expectedSignature) {
    echo "Signature is valid\n";
} else {
    echo "INVALID SIGNATURE!\n";
}

node.js

const crypto = require('crypto');

const jsonMessage = '[CALLBACK MESSAGE]';
const signatureKey = '[YOUR SIGNATURE KEY]';

const jsonNode = JSON.parse(jsonMessage);
const resultElement = jsonNode.result || {};
const expectedSignature = jsonNode.signature || '';

const keys = {};

// Collect and format values
for (const [key, value] of Object.entries(resultElement)) {
    if (value === null || value === undefined) continue;

    let valueStr;
    if (key === 'amount' || key === 'commission') {
        valueStr = parseFloat(value).toFixed(2); // Always two decimals
    } else {
        valueStr = String(value);
    }

    if (valueStr.trim() !== '') {
        keys[key] = valueStr;
    }
}

// Sort keys case-insensitively
const orderedKeys = Object.keys(keys).sort((a, b) =>
    a.toLowerCase().localeCompare(b.toLowerCase())
);

// Join values with colon
const additionalString = orderedKeys.map(k => keys[k]).join(':');
const hashInput = `${additionalString}:${signatureKey}`;

// Hash and base64 encode
const hash = crypto.createHash('sha256').update(hashInput, 'utf8').digest('base64');

Last updated