OpenVibe.Events delivers each event as POST with the body {"event": <envelope>, "seq": <n>} and these headers, signed with your subscription secret:
X-OpenVibe-Signature: sha256=<hex HMAC-SHA256 of the raw body> (v1).
X-OpenVibe-Timestamp: <unix seconds>, the time this attempt was sent. Every retry gets a new one.
X-OpenVibe-Signature-V2: t=<that timestamp>,v2=<hex HMAC-SHA256 of "<t>.<raw body>"> (v2).
Verify against the raw bytes, before parsing, with a constant-time comparison. v1 covers only the body, so a captured delivery verifies forever. v2 also covers the time: refuse it when t is more than 300 seconds from your clock, in either direction. When the v2 header is present but does not verify or is stale, reject the delivery. Never fall back to v1. parseDelivery(raw, headers, secret, { requireV2: true }) in openvibe-sdk/events (0.4.0 and later) does all of this, and also refuses deliveries that carry only v1. verifyDelivery() checks v1 only; verifyDeliveryV2() checks v2 and the window. The tester below checks both, says whether the timestamp is inside the window now, and decides as a receiver that requires v2 does: v1 is shown for reference and never decides.