Skip to content

Troubleshooting a webhook signature mismatch

development

A signature mismatch means the received message did not verify under the supplied secret and protocol. Investigate body handling and endpoint configuration before assuming the provider sent invalid data.

Gather the details you will need

Use a test delivery and preserve its non-sensitive delivery identifier and error time. Keep signing secrets and full sensitive payloads out of shared logs.

01 Troubleshoot a webhook signature mismatch

  1. Confirm the secret belongs to this exact endpoint and environment. A command-line test endpoint, dashboard endpoint and production endpoint may use different secrets.

  2. Check that the application verifies the original body bytes required by the provider. JSON parsing and reserialization can change whitespace or ordering and break verification.

  3. Read the correct signature header and algorithm through the provider’s maintained library. Stripe and GitHub, for example, use different signature formats.

  4. Review timestamp tolerance and server clock where the verification protocol includes time. Do not disable replay protection permanently to accommodate a clock problem.

  5. Check middleware, proxy and encoding behavior, then repeat a genuine test delivery after the correction.

02 Example: preserve the original Stripe webhook body

For an existing PHP endpoint using Stripe’s maintained SDK, verify the original request bytes before JSON processing. The signing secret must come from the protected configuration for this exact test or production endpoint. This is the verification stage of the handler, not a complete payment workflow.

Code or configuration example
$secret = getenv('STRIPE_WEBHOOK_SECRET');
if (!is_string($secret) || $secret === '') {
    http_response_code(500);
    exit;
}
$rawBody = file_get_contents('php://input');
try {
    $event = \Stripe\Webhook::constructEvent(
        $rawBody,
        $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '',
        $secret
    );
} catch (\UnexpectedValueException | \Stripe\Exception\SignatureVerificationException $e) {
    http_response_code(400);
    exit;
}

Continue only with the verified event, its expected account and event type, and durable duplicate protection. Do not encode an already parsed JSON object back into a string for verification; spacing and ordering can change the signed bytes.

Expected result: A genuine test delivery verifies. Changing a single payload character makes verification fail before any order, email or provisioning action is performed.

Confirm the expected result

Confirm the genuine event verifies and a modified payload fails. Verify that the invalid request cannot reach the business action handler.

If the problem continues

If only production fails, compare the endpoint secret, raw-body middleware order and any platform request transformation. Repeatedly rotating secrets will not fix a body parser that changes signed bytes.

A common question

Can I compare signatures using ordinary string equality?

Use the provider library or an appropriate constant-time verification implementation for the specified protocol. Avoid a custom comparison and parsing design when a maintained implementation exists.

Was this answer helpful?

Need a little more help?

Our support team can help you find the next step.

Contact support