diff --git a/src/Webhook.php b/src/Webhook.php index 84356fe..d56318e 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -26,15 +26,42 @@ final class Webhook * @param string $rawBody Corpo bruto EXATO da requisição (não re-serialize o JSON). * @param string|null $signatureHeader Valor do header `X-SMSGo-Signature`. * @param string $secret Segredo do webhook (retornado por `setWebhook`/`getWebhook`). + * @param int|null $toleranceSeconds Se informado, além da assinatura exige que o + * `sentAt` do corpo esteja dentro desta janela (anti-replay). Sem ele, o + * comportamento é idêntico ao anterior (só assinatura). Deduplicar pelo `id` + * do corpo continua a cargo do receptor. */ - public static function verifySignature(string $rawBody, ?string $signatureHeader, string $secret): bool - { + public static function verifySignature( + string $rawBody, + ?string $signatureHeader, + string $secret, + ?int $toleranceSeconds = null + ): bool { if ($signatureHeader === null || $signatureHeader === '') { return false; } $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret); - return hash_equals($expected, $signatureHeader); + if (!hash_equals($expected, $signatureHeader)) { + return false; + } + + if ($toleranceSeconds !== null) { + $payload = json_decode($rawBody, true); + $sentAt = is_array($payload) && isset($payload['sentAt']) ? $payload['sentAt'] : null; + if (!is_string($sentAt)) { + return false; + } + $ts = strtotime($sentAt); + if ($ts === false) { + return false; + } + if (abs(time() - $ts) > $toleranceSeconds) { + return false; + } + } + + return true; } }