> For the complete documentation index, see [llms.txt](https://docs.cuoral.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cuoral.com/authentication.md).

# Authentication

Cuoral uses **HMAC-SHA256 signatures** to authenticate webhook requests and ensure that webhook events are coming from Cuoral.

Every webhook request includes a signature in the `X-Cuoral-Webhook-Signature` header.

Your webhook endpoint should verify this signature before processing the request.

### Signature Header

The signature is provided in the following request header:

```http
X-Cuoral-Webhook-Signature: <signature>
```

The signature is generated using:

* **Algorithm:** HMAC-SHA256
* **Secret:** Your organization's `api_key`
* **Payload:** The webhook request payload serialized as JSON with sorted keys

### How Verification Works

When Cuoral sends a webhook, it:

1. Serializes the webhook payload as JSON with keys sorted alphabetically.
2. Uses your organization's `api_key` as the HMAC secret.
3. Generates an HMAC-SHA256 hash.
4. Sends the resulting hexadecimal signature in the `X-Cuoral-Webhook-Signature` header.

Your application should perform the same calculation and compare the generated signature with the value provided in the request header.

If the signatures match, the webhook can be considered authentic.

### Node.js

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signatureHeader, publicKey) {
  const payloadString = JSON.stringify(
    payload,
    Object.keys(payload).sort()
  );

  const expectedSignature = crypto
    .createHmac('sha256', publicKey)
    .update(payloadString)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(signatureHeader)
  );
}
```

### Python

```python
import hmac
import hashlib
import json

def verify_webhook_signature(
    payload: dict,
    signature_header: str,
    public_key: str
) -> bool:
    payload_str = json.dumps(payload, sort_keys=True)

    expected_signature = hmac.new(
        public_key.encode("utf-8"),
        payload_str.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(
        expected_signature,
        signature_header
    )
```

### Important: Verify the Raw Payload

For maximum reliability, webhook signature verification should be performed against the **raw request body** before your framework parses or transforms the JSON.

Re-serializing JSON can produce a different string due to differences in whitespace, escaping, or key ordering.

If your framework provides access to the raw request body, use it when calculating the HMAC.

### Security Recommendations

* Always verify the `X-Cuoral-Webhook-Signature` header before processing a webhook.
* Keep your organization's api\_key secret.
* Use HTTPS for your webhook endpoint.
* Use a constant-time comparison when comparing signatures.
* Return a successful HTTP status only after the webhook has been accepted and validated.
