" */ public static function sign( $secret, $msg_id, $timestamp, $body ) { $signed = $msg_id . '.' . (int) $timestamp . '.' . $body; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Signature encoding required by the specification. return 'v1,' . base64_encode( hash_hmac( 'sha256', $signed, self::key( $secret ), true ) ); } /** * Signing headers for a request. * * @param string $secret Secret. * @param string $msg_id Message ID (stable across retries). * @param string $body Raw request body. * @return array */ public static function headers( $secret, $msg_id, $body ) { $timestamp = time(); return array( 'webhook-id' => $msg_id, 'webhook-timestamp' => (string) $timestamp, 'webhook-signature' => self::sign( $secret, $msg_id, $timestamp, $body ), ); } /** * Verify a signature (useful for receivers written in PHP and for tests). * * @param string $secret Secret. * @param string $msg_id Message ID. * @param int $timestamp Timestamp. * @param string $body Body. * @param string $header Signature header, possibly with several space separated signatures. * @param int $tolerance Allowed clock skew in seconds. * @return bool */ public static function verify( $secret, $msg_id, $timestamp, $body, $header, $tolerance = 300 ) { if ( abs( time() - (int) $timestamp ) > $tolerance ) { return false; } $expected = self::sign( $secret, $msg_id, $timestamp, $body ); foreach ( explode( ' ', (string) $header ) as $candidate ) { if ( hash_equals( $expected, trim( $candidate ) ) ) { return true; } } return false; } }