## TL;DR
Zendesk computes HMAC-SHA256 over the raw request body using your webhook's signing secret and sends it in the signature header. Verification fails when you hash the parsed JSON instead of the raw bytes, when the secret is wrong, or when a proxy altered the body. Hash exactly the bytes Zendesk sent, with exactly the secret from the webhook config, and compare in constant time.

## The query

```text
zendesk webhook signature verification failed
```

## Use this when

- Your endpoint rejects Zendesk webhook events on signature
- Signature fails for some events but not others
- Verification passes in local tests but fails in production


## Not for

- Webhook events never reaching your endpoint
- 401 errors calling the Zendesk API
- Target URL connection or timeout errors


## Steps

### 1. Use the raw body, not the parsed JSON

Grab the body before your framework parses it. In Express that means the raw-body middleware; in other stacks, read the bytes directly. Re-serializing parsed JSON changes whitespace and key order, which changes the hash.

Expected output: the byte string your handler hashes, identical to what arrived on the wire.

### 2. Confirm the signing secret matches the webhook

Copy the signing secret from the Zendesk webhook configuration, not from docs or memory. Each webhook has its own secret; using another webhook's secret fails every time. Check for pasted whitespace.

Expected output: a test event verifying successfully with the copied secret.

### 3. Compute HMAC-SHA256 exactly like Zendesk

Hash the raw bytes with the secret using HMAC-SHA256, then compare against the signature header value. Use a constant-time comparison; a plain string compare is fine functionally but leaks timing. Mind hex vs base64 encoding of the digest.

Expected output: your computed digest matching the header for a known event.

### 4. Check for body-mutating middleware in production

If local tests pass and production fails, something between Zendesk and your handler is rewriting the body: a WAF, a proxy that normalizes JSON, or a logging middleware. Compare the Content-Length header against the bytes you received.

Expected output: identical byte counts at the edge and in your handler.

## Variant phrasings

### zendesk webhook hmac signature invalid

Steps 1 through 3 cover the full verification recipe.

### zendesk signatures fail after moving to a new server

Step 4. New infra almost always adds a proxy that touches the body.

## Why it happens

Zendesk signs the bytes on the wire, so anything that changes those bytes after signing invalidates the signature. Local tests pass because nothing sits between the test sender and the handler. Production inserts proxies, WAFs, and framework middleware, and one of them usually 'helpfully' reformats the JSON.

## Edge cases

- Secret rotation: Zendesk lets you rotate the signing secret. During rotation, accept both old and new for a window.
- Replayed events: signatures don't expire. Add your own timestamp check if replay matters to you.
- Multiple webhooks to one endpoint: each has its own secret. Route by webhook id before verifying.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_joESuN-H3rBicXqgl_UZ3Q
