openai/openai-python · critical · InvalidWebhookSignatureError
The given webhook signature does not match the expected sign
Error message
The given webhook signature does not match the expected signature
What it means
After resolving the secret, verify_signature recomputes the HMAC signature of the raw payload (with a timestamp, within the tolerance window) and compares it to the webhook-openai-signature header. A mismatch means the payload, headers, or secret do not correspond to what OpenAI signed, so the SDK raises InvalidWebhookSignatureError to prevent accepting forged or tampered requests. Note the 'from None' suppresses the underlying cause to avoid leaking signature details.
Source
Thrown at src/openai/resources/webhooks/webhooks.py:66
"""Validates whether or not the webhook payload was sent by OpenAI.
Args:
payload: The webhook payload
headers: The webhook headers
secret: The webhook secret (optional, will use client secret if not provided)
tolerance: Maximum age of the webhook in seconds (default: 300 = 5 minutes)
"""
if secret is None:
secret = self._client.webhook_secret
if secret is None:
raise ValueError(
"The webhook secret must either be set using the env var, OPENAI_WEBHOOK_SECRET, "
"on the client class, OpenAI(webhook_secret='123'), or passed to this function"
)
if not _webhook_signature_matches(payload, headers, secret=secret, tolerance=tolerance):
raise InvalidWebhookSignatureError(
"The given webhook signature does not match the expected signature"
) from None
class AsyncWebhooks(AsyncAPIResource):
def unwrap(
self,
payload: str | bytes,
headers: HeadersLike,
*,
secret: str | None = None,
) -> UnwrapWebhookEvent:
"""Validates that the given payload was sent by OpenAI and parses the payload."""
if secret is None:
secret = self._client.webhook_secret
self.verify_signature(payload=payload, headers=headers, secret=secret)
View on GitHub (pinned to 9917c6e28e)
Solutions
- Use the raw request body bytes exactly as received (e.g. express.raw middleware, req.body as Buffer), not re-serialized JSON
- Confirm the secret matches the webhook endpoint configured in the OpenAI dashboard
- Forward the original signature and timestamp headers unmodified
- If developing locally with a fake signature, call unwrap/verify with a properly computed test signature instead of hand-crafting headers
Example fix
# before (payload re-serialized -> signature mismatch) data = json.loads(raw) client.webhooks.unwrap(json.dumps(data).encode(), headers) # after (raw bytes preserved) client.webhooks.unwrap(raw, headers)
Defensive patterns
Strategy: try-catch
Try / catch
from openai import InvalidWebhookSignatureError
try:
event = client.webhooks.unwrap(payload, headers)
except InvalidWebhookSignatureError:
return Response("Invalid signature", status_code=400) Prevention
- Verify against the raw request body bytes, never re-serialized JSON
- Configure raw-body middleware before JSON parsers (e.g. express.raw)
- Forward all webhook headers verbatim from your proxy
- Keep server clocks NTP-synced to avoid timestamp tolerance failures
- Log failures without echoing the secret or expected signature
When it happens
Trigger: Sending the wrong secret (e.g. a test secret against production webhooks), re-serializing the JSON payload instead of using the raw request bytes, missing/corrupted signature or timestamp headers, or a payload modified in transit or by middleware.
Common situations: Frameworks that parse and re-encode JSON before the handler sees the raw body (Express without express.raw, Next.js route handlers re-stringifying), proxy middleware altering whitespace, or simply copying the wrong secret from the dashboard.
Related errors
- The webhook secret must either be set using the env var, OPE
- Constructed path {path_result!r} contains dot-segment {segme
- Invalid webhook timestamp format
- Webhook timestamp is too old
- Webhook timestamp is too new
AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28).
Data as JSON: /api/errors/4d1d878358f387a1.
Report an issue: GitHub.