> Part of [WHCC Developer Documentation](https://www.whcc.com/developer/llms.txt)

# Webhook Security

WHCC signs every webhook request it sends to your endpoint by including a `WHCC-Signature` header.

> The string that gets signed is **different for V1 and V2** integrations. Using the wrong one is the most common cause of signature validation failing. Check [which version you're on](https://www.whcc.com/#version) before following Step 2 below.

> If you have an [Editor API](https://www.whcc.com/developer/docs/editor-api/index.html.md) integration, WHCC provides an [endpoint](https://www.whcc.com/developer/docs/editor-api/webhooks/#validate) to make signature validation simple and straightforward.

## Signature verification

The `WHCC-Signature` header included in each webhook event contains a timestamp and one or more signatures. The timestamp is prefixed by `t=`
and each signature is prefixed by a version `v`, followed by an integer. Currently, the only valid version is `v1`.

You can see a sample signature below:

```shell
   WHCC-Signature:
   t=1591735205,
   v1=307D88AF1425DC58552C1A6EDFB4C95E3E989F5B878CAFFEFFA6D581578DC82A
```

> Newlines have been added to the above header for readability purposes only, an actual `WHCC-Signature` will be on a single line.

> The `v1` digest is **uppercase** hexadecimal. Most HMAC libraries produce lowercase, so compare case-insensitively (or normalise both sides) &mdash; a direct string comparison against a lowercase digest will fail on every request.
>
> Compare using a **constant-time** comparison function, not `==`, so that a mismatch cannot be located one byte at a time.
>
> If your consumer record has no signing secret configured, the `WHCC-Signature` header is sent **empty** rather than omitted. Treat an empty signature as a verification failure, and contact WHCC support to have a signing secret configured if you receive one.

### Step 1: Extract timestamp and signature from the header

Split the header, using the `,` character as the separator, to get a list of elements. Then split each element, using the `=` character as the separator, to get a prefix and value pair.

The value for the prefix `t` corresponds to the timestamp, and `v1` corresponds to the signature (or signatures). You can discard all other elements.

### Step 2: Prepare the payload

The string to be hashed is assembled by concatenating:

What that request body looks like depends on your webhook version:

#### V2 (JSON)

If your integration receives `Content-Type: application/json` requests, the signed content is the JSON payload as a string &mdash; i.e. the raw request body.

#### V1 (form-encoded)

If your integration receives `Content-Type: application/x-www-form-urlencoded` requests (see [V1 Webhook Format](https://www.whcc.com/developer/docs/order-submit-api/webhook-v1-format/index.html.md)), the signed content is the **entire form-encoded body** &mdash; the full `Message=…&EntryId=…&json=…` string exactly as received, not the JSON payload's data or the decoded `json` field alone. Do not re-serialize or re-encode the body before hashing it; use the bytes as received.

> For both versions, do not parse and re-serialize the body before hashing. Any change to whitespace, field order, or encoding &mdash; even one that looks equivalent &mdash; produces a different signature.

### Step 3: Compute the expected signature

Compute an HMAC with the `SHA256` hash function. The key will be your consumer secret and the message is the assembled string from the previous step.

### Step 4: Compare the signatures

Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

## Preventing common attacks

### Replay Attacks

To protect against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack), where an attacker intercepts a valid payload and its signature, then re-transmits them, you can set a tolerance for the timestamp included in the signature.

### Downgrade Attacks

To protect against [downgrade attacks](https://en.wikipedia.org/wiki/Downgrade_attack), you should ignore all signatures that are not `v1`

### Timing attacks

To protect against [timing attacks](https://en.wikipedia.org/wiki/Timing_attack), be sure to use a constant-time string comparison to compare the expected signature to the received signature.