This is what the ETag header was designed for. A very common way to implement it is to produce your response payload, make a hash of it (it doesn't have to be secure, just low-collision), and then use that hash as the value of the ETag. Note that this approach is ignorant of how many sources are involved in producing the response.
The client then sends the received ETag back in an If-Match header, which the server can use to check the freshness of the request.