openai/openai-python · error · InvalidWebhookSignatureError

Webhook timestamp is too old

Error message

Webhook timestamp is too old

What it means

Anti-replay protection in webhook_signature_matches: the signed webhook-timestamp is older than the allowed tolerance (default 5 minutes) relative to server time, so the signature is rejected as a possible replay.

Source

Thrown at src/openai/lib/_webhooks.py:34

    *,
    secret: str,
    tolerance: int,
) -> bool:
    """Validate the replay window and compare the supplied signatures."""
    signature_header = get_required_header(headers, "webhook-signature")
    timestamp = get_required_header(headers, "webhook-timestamp")
    webhook_id = get_required_header(headers, "webhook-id")

    # Validate timestamp to prevent replay attacks
    try:
        timestamp_seconds = int(timestamp)
    except ValueError:
        raise InvalidWebhookSignatureError("Invalid webhook timestamp format") from None

    now = int(time.time())

    if now - timestamp_seconds > tolerance:
        raise InvalidWebhookSignatureError("Webhook timestamp is too old") from None

    if timestamp_seconds > now + tolerance:
        raise InvalidWebhookSignatureError("Webhook timestamp is too new") from None

    # Extract signatures from v1,<base64> format
    # The signature header can have multiple values, separated by spaces.
    # Each value is in the format v1,<base64>. We should accept if any match.
    signatures: list[str] = []
    for part in signature_header.split():
        if part.startswith("v1,"):
            signatures.append(part[3:])
        else:
            signatures.append(part)

    # Decode the secret if it starts with whsec_
    if secret.startswith("whsec_"):
        decoded_secret = base64.b64decode(secret[6:])
    else:

View on GitHub (pinned to 9917c6e28e)

Solutions

  1. Sync the receiving server's clock (NTP/chrony)
  2. If you legitimately process delayed deliveries (queues), pass a larger tolerance in seconds to webhook_signature_matches
  3. Do not re-process captured old requests; fetch fresh state instead

Example fix

# before
webhook_signature_matches(body, headers, secret)
# after
webhook_signature_matches(body, headers, secret, tolerance=3600)
Defensive patterns

Strategy: try-catch

Validate before calling

ts = int(headers["webhook-timestamp"])
import time
if time.time() - ts > tolerance:
    # queue-delayed webhook: widen tolerance or fetch fresh state

Try / catch

try:
    ok = webhook_signature_matches(body, headers, secret, tolerance=3600)
except InvalidWebhookSignatureError as e:
    if "too old" in str(e):
        return Response(408)
    raise

Prevention

When it happens

Trigger: Receiving a webhook more than tolerance seconds after it was signed; also caused by server clock skew, or replaying/queuing old webhook deliveries.

Common situations: Queue backlog delaying delivery; NTP drift on the receiving host; processing retries of old requests; deliberately re-sending captured payloads.

Related errors


AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28). Data as JSON: /api/errors/11868d80542ebed6. Report an issue: GitHub.