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

  1. Use the raw request body bytes exactly as received (e.g. express.raw middleware, req.body as Buffer), not re-serialized JSON
  2. Confirm the secret matches the webhook endpoint configured in the OpenAI dashboard
  3. Forward the original signature and timestamp headers unmodified
  4. 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

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


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