Nothing in the current check. Sign a timestamp with the body, reject old timestamps, and remember event ids you've already handled
A signature only proves who made the message, not when. An old message with a valid signature is still valid. This is a replay attack. Providers like Stripe sign timestamp.body together and suggest rejecting messages older than a few minutes. Saving each event id you've handled stops replays inside that window. It also makes normal retries safe, because retries resend the same event.
"The timing-safe compare rejects a signature it has seen before" is the tempting wrong answer. It only compares two values in constant time, and it has no memory. The HMAC doesn't change over time unless you sign something that does, like a timestamp.
Read more