X-Telehealth-Signature en hexadecimal minúsculas (sin prefijo). Debe calcular HMAC-SHA256 del cuerpo raw (UTF-8) con el secret y comparar en hex minúsculas.
- PHP
- Python
- Node.js
- Java
Copiar
<?php
function verifyWebhookSignature(string $rawBody, string $signatureHeader, string $secret): bool {
$expected = hash_hmac('sha256', $rawBody, $secret, false); // false = hex en minúsculas
return hash_equals($expected, $signatureHeader);
}
// En su endpoint que recibe el webhook:
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_TELEHEALTH_SIGNATURE'] ?? '';
$secret = 'SU_WEBHOOK_SECRET';
if (!verifyWebhookSignature($rawBody, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
// Procesar $payload según $payload['event'] o header X-Telehealth-Event
Copiar
import hmac
import hashlib
def verify_webhook_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)
# En su endpoint que recibe el webhook (ej. Flask):
# raw_body = request.get_data()
# signature = request.headers.get("X-Telehealth-Signature", "")
# if not verify_webhook_signature(raw_body, signature, WEBHOOK_SECRET):
# return "", 401
# payload = request.get_json(force=True, silent=True)
Copiar
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signatureHeader, 'hex')
);
}
// En su endpoint (ej. Express): usar express.raw() para esta ruta para tener el body sin parsear
// const rawBody = req.body; // Buffer
// const signature = req.headers['x-telehealth-signature'] || '';
// if (!verifyWebhookSignature(rawBody.toString('utf8'), signature, process.env.WEBHOOK_SECRET)) {
// return res.status(401).send('Invalid signature');
// }
// const payload = JSON.parse(rawBody.toString('utf8'));
Copiar
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public static boolean verifyWebhookSignature(String rawBody, String signatureHeader, String secret)
throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] hash = mac.doFinal(rawBody.getBytes(StandardCharsets.UTF_8));
String expected = HexFormat.of().formatHex(hash).toLowerCase();
return java.security.MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signatureHeader.getBytes(StandardCharsets.UTF_8)
);
}
// En su controller: leer el body raw (sin parsear a JSON antes), luego verificar y después parsear.
