Webhook replay and receiver deduplication

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:

Python
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.