Webhook replay and receiver deduplication
Verify signed bytes and recover delivery failures without repeating completed side effects.
Webhook replay resends an existing event with a new delivery attempt. The event identity remains stable so a receiver can distinguish retransmission from a new infrastructure change.
Verify before parsing
Compute the signature over the exact request bytes, not a reserialized JSON
object. Header timestamp values use Unix seconds. Reject a timestamp outside
300 seconds of the receiver's synchronized clock before acting on the payload.
The following Python helper requires the raw request body, header values, current
Unix time, and the endpoint's signing secret. Use it to verify one v1 signature:
import hashlib
import hmac
def verify_delivery(secret, raw_body, timestamp, signature, now):
if not isinstance(timestamp, str) or not timestamp.isascii():
return False
if not isinstance(signature, str) or not signature.isascii():
return False
if not timestamp.isdecimal():
return False
try:
received_at = int(timestamp)
except (TypeError, ValueError):
return False
if abs(now - received_at) > 300:
return False
signed = timestamp.encode("ascii") + b"." + raw_body
expected = "v1=" + hmac.new(
secret, signed, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)During key overlap, verify against the accepted secret versions. A new signing secret does not change the event ID. Treat a signature failure as an unauthenticated delivery rather than a reason to query arbitrary resource IDs supplied in that payload.
Persist the event once
Store the event ID and payload durably before returning 2xx. A duplicate event
whose previous processing completed should be acknowledged without repeating
its side effects. A duplicate whose processing failed can resume the recorded
local job under the receiver's own retry policy.
Do not mark an event complete merely because its HTTP request was accepted by a queue. Completion describes the business action, while durable receipt describes whether Infra can stop retrying network delivery.
Replay a rejected attempt
Open Settings → Integrations → Webhooks → Endpoint → Deliveries and read the failed attempt's receiver response. Correct that error, then submit its delivery ID to the replay operation. Follow the returned operation and the new delivery's receiver status to confirm acceptance.
The new attempt has a fresh timestamp and signature but retains the event ID. It can pass signature freshness checks and still be a duplicate. Deduplicate completed processing by event ID; resume a failed job under the receiver's own retry policy.
The replay request identifies the destination and original event. It cannot fabricate an event payload or convert a historical event into current resource state. A receiver should reconcile the relevant operation or resource before performing another mutation.
Retention and gaps
An event no longer retained for replay returns event_expired. Query current
resources or a bounded audit export to rebuild the receiver's inventory. Current
state cannot reproduce every transient historical event or external side effect.
Use Webhook API for subscription and delivery endpoints, webhook delivery rules for retries, and API idempotency for receiver-initiated mutations.