secret = $secret; } /** * Add the timestamp and signature headers. * * @param array $headers Headers assembled so far. * @param string $body The exact request body. * @return array */ public function sign( array $headers, string $body ): array { if ( '' === $this->secret ) { return $headers; } // Seconds, and the site's own clock. A server drifting more than five minutes from the // collector will see stale_timestamp rather than a signature failure, which is the difference // between "fix NTP" and "your key is wrong". $timestamp = (string) time(); // The signed string is "." — joining them means a captured signature cannot be // lifted onto a different body, and a captured body cannot be replayed under a fresh timestamp. $signature = hash_hmac( 'sha256', $timestamp . '.' . $body, $this->secret ); $headers['X-Sov-Timestamp'] = $timestamp; $headers['X-Sov-Signature'] = 'v1=' . $signature; return $headers; } }