# Verify the signature

`X-Connect-Signature-256: sha256=HMAC_SHA256(secret, "<X-Connect-Timestamp>.<raw body>")`. Compute the HMAC over the timestamp + "." + the **raw bytes** of the request (re-serializing the JSON changes the hash) and compare it in **constant time**. The timestamp is INSIDE the HMAC → a captured payload cannot be replayed; reject deliveries older than 5 minutes. The `secret` (`whsec_…`) is shown ONCE when you create the endpoint.

## Examples

### cURL

```bash
# Verify X-Connect-Signature-256 on the server that RECEIVES the webhook.
# The header is: sha256=HMAC_SHA256(secret, "<X-Connect-Timestamp>.<raw body>")
# Recompute it over the timestamp + "." + the EXACT raw request body and compare.
# (Shell alone can't compare in constant time — use one of the snippets below.)
echo -n "${TIMESTAMP}.${RAW_BODY}" | openssl dgst -sha256 -hmac "whsec_YOUR_ENDPOINT_SECRET"
```

### Node.js

```javascript
import crypto from "crypto";
const SECRET = "whsec_YOUR_ENDPOINT_SECRET";

function verify(req) {
  const ts = req.header("X-Connect-Timestamp");
  const sig = req.header("X-Connect-Signature-256") || "";
  // Reject anything older than 5 minutes (anti-replay).
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = "sha256=" + crypto
    .createHmac("sha256", SECRET)
    .update(ts + "." + req.rawBody)   // req.rawBody = the EXACT bytes received
    .digest("hex");
  const a = Buffer.from(sig), b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

### PHP

```php
<?php
$SECRET = "whsec_YOUR_ENDPOINT_SECRET";

function verify(string $rawBody, array $headers): bool {
  $ts  = $headers["X-Connect-Timestamp"] ?? "";
  $sig = $headers["X-Connect-Signature-256"] ?? "";
  if (abs(time() - (int)$ts) > 300) return false;        // anti-replay: 5 min
  $expected = "sha256=" . hash_hmac("sha256", $ts . "." . $rawBody, $GLOBALS["SECRET"]);
  return hash_equals($expected, $sig);                    // constant-time compare
}
```

### Python

```python
import hmac, hashlib, time

SECRET = b"whsec_YOUR_ENDPOINT_SECRET"

def verify(raw_body: bytes, headers) -> bool:
    ts  = headers.get("X-Connect-Timestamp", "")
    sig = headers.get("X-Connect-Signature-256", "")
    if abs(time.time() - int(ts)) > 300:      # anti-replay: 5 min
        return False
    mac = hmac.new(SECRET, (ts + ".").encode() + raw_body, hashlib.sha256)
    expected = "sha256=" + mac.hexdigest()
    return hmac.compare_digest(expected, sig)  # constant-time compare
```

## Notes

- This is where most people get stuck and where many end up validating nothing (a security hole). This code is complete and correct in all four languages.
- The real `secret` comes from your endpoint in Webhooks (shown once; you can rotate it).
