How to correct the phase offset for QPSK I-Q data
Assuming we have timing recovery resolved (meaning our system knows the correct time locations for our symbol decisions), then we can use the decided symbols along with our pre-decision values in a Decision-Directed Phase Detector. This will give us an error value that we can then feed into a phase lock loop to correct for phase error using traditional PLL methods.
Decision Directed Phase Detector
See my figure below for the implementation of a decision directed phase detector. The two vectors shown are the complex sample before making a decision ($V_2$), and the complex sample after the decision, which represents the closest location for each symbol in our locked constellation ($V_1). For QPSK, the normalized decisions would be 0.707 ± j0.707.

This describes a very simple approach to get a phase error term for QPSK (and higher order QAM systems) as an implementation of the phase discriminator. This is in fact a compact representation of the phase detector in the Costas Loop for a QPSK demodulator. The rest of the work for tracking assumes that you have knowledge in Digital PLL implementations to &quot;close the loop&quot; and is not covered here.
To explain the Decision Directed Phase Detector further for those extremely interested, I detailed below the more generic &quot;cross product phase detector&quot;. Understanding this will give us full insight into the operation summarized in the figure above.
Cross Product Phase Detector
The cross product phase detector works based on the property that the imaginary portion (cross product) of a complex conjugate multiplication of two vectors is directly proportional to the sine of the phase angle between the vectors.  This has wide utility in digital carrier and phase tracking algorithms.  The easy way to see this is knowing that when we multiply two vectors, the angles add; so if we conjugate one of the vectors, the angles will subtract giving us the difference of the two angles.

The math for the actual complex conjugate multiplication reveals how simple the final  implementation can be (two real multiplies and an add), which is what makes this approach so prevalent:
Consider two vectors $V_1$ and $V_2$
$V1= I_1+jQ_1$
$V2= I_2+jQ_2$
$V_1^*V_2 = (I_1-jQ_1)(I_2+jQ_2) = (I_1I_2+Q1Q2) + j(I_1Q_2-I_2Q_1) = I_P+jQ_P$
The angle of the result is $\phi = atan2(Q_P,I_P)$
However in most cases for this application the processing intensive atan2 is not necessary, as we can make use of certain approximations as follows:
First the imaginary term $Q_P$ is proportional to the sine of the angle:
$Q_P = |V_1||V_2|sin(\phi)$
The proportionality constant just becomes part of our overall loop gain; for a phase detector we just need something that is linearly proportional to phase.
Second for small angles, $sin(\phi) \approx \phi$
A loop would drive the angle (error) to 0, therefore when tracking the angle will be small, so this approximation is valid for assessing the tracking performance of the loop. For acquisition considerations, we need to be aware of the reduced loop gain due to the changing slope of a sine function (unless calculating an atan which has the benefit of being linear throughout its range).
So bottom line for a cross product phase detector we take advantage of the following relationship between the I and Q values of two complex samples:
Given two complex samples [$I_1,Q_1$] and [$I_2,Q_2$], the cross product of the two samples is proportional to the phase between them:
$I_1Q_2-I_2Q_1 \propto \phi$
Thus we have provided a very simple and elegant way to provide phase discrimination in an all-digital PLL.
Note: The following result is rotation invariant, meaning if the constellation rotates past 45 degree error, it will lock in another position. A known training sequence (in the header) would be required to establish the correct orientation for your packet of data.