> For the complete documentation index, see [llms.txt](https://docs.quadrata.com/integration/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.quadrata.com/integration/how-to-integrate/webhooks/webhook-request-signature.md).

# Webhook Request Signature

Each webhook request that Quadrata emits will contain a header signature that an integrating application can use to verify the authenticity of the message.

Request signatures are sent as `Base64 Encoded` `SHA384` strings, in the `X-WEBHOOK-SIGNATURE` request header.

{% hint style="info" %}
The signature message used to sign and verify is the stringified JSON body payload.
{% endhint %}

## Verifying The Request Signature

To verify the request signature, use Quadrata's public signing key for the respective environment.

### Quadrata Public Signing Keys

{% tabs %}
{% tab title="Staging" %}

```
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----
```

{% endtab %}

{% tab title="Production" %}

```
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOuY3rbyrujXxVEWq2X70uRa53ySTjwKR
j1ueDjYuzMegLrxIRiCXWMPtrVuqE0FcZ2YmJSiTaoDsq4yYMJw7fxi6nUj/8bzT
4+IxIok9qaEq9IbX6Bo/95vAu5bwO3rf
-----END PUBLIC KEY-----
```

{% endtab %}
{% endtabs %}

### Code Samples

{% tabs %}
{% tab title="NodeJS: Crypto" %}

```javascript
// NOTE: this is using Quadrata's Staging webhook signing key
const publicKey = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----`;

/**
 * Verify a signed message
 * @param {string} message The request body stringified
 * @param {string} signature The base64 encoded signature
 * @returns {boolean}
 */
function verifySignature(message, signature) {
  const signature = Buffer.from(signature, 'base64');

  // create a verifier and verify the signature
  const verifier = crypto.createVerify('sha384');
  verifier.update(message);
  
  return verifier.verify(publicKey, signature);
}
```

{% endtab %}

{% tab title="NodeJS: Express + Crypto" %}

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

// NOTE: this is using Quadrata's Staging webhook signing key
const publicKey = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----`;

/**
 * Verify a signed message
 * @param {string} message The request body stringified
 * @param {string} signature The base64 encoded signature
 * @returns {boolean}
 */
function verifySignature(message, signature) {
  const signature = Buffer.from(signature, 'base64');

  // create a verifier and verify the signature
  const verifier = crypto.createVerify('sha384');
  verifier.update(message);
  
  return verifier.verify(publicKey, signature);
}

// initialize the express application
const app = express();

// parse JSON request body into req.body
app.use(express.json());

// webhook request handler
app.post('/quadrata-webhook', (req, res) => {
  
  // verify the request has a webhook signature
  if (!('x-webhook-signature' in req.headers) 
      || !req.headers['x-webhook-signature']
  ) {
    return res.status(400).send();
  }
  
  // stringify the request body
  const signatureMessage = JSON.stringify(req.body);
  
  // extract the base64 signature from headers
  const signature = request.headers['x-webhook-signature'];
  
  // verify the signature
  if (!verifySignature(signatureMessage, signature)) {
    return res.status(400).send();
  }
  
  // webhook is valid, flush a successful response
  res.status(200).send();
  
  // process the event on your servers (you implement this)
  processQuadrataWebhook(req.body)
    .catch(console.error);

});

// listen for traffic
const port = process.ENV.port;
const hostname = process.ENV.hostname;
app.listen(port, hostname);
```

{% endtab %}

{% tab title="Python" %}

```python
import base64

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, utils

# NOTE: using Quadrata's Staging webhook signing key
PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----"""

def verify_signature(message: str, b64_signature: str) -> bool:
    public_key = serialization.load_pem_public_key(
        PUBLIC_KEY_PEM.encode('utf-8')
    )
    try:
        public_key.verify(
            base64.b64decode(b64_signature),
            message.encode('utf-8'),
            ec.ECDSA(hashes.SHA384()),
        )
        return True
    except:
        return False
```

{% hint style="warning" %}
If using `json.dumps` to marshal the JSON body payload into a string, be sure to override the separators:&#x20;

`json.dumps(request_body, separators=(',', ':'))`
{% endhint %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.quadrata.com/integration/how-to-integrate/webhooks/webhook-request-signature.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
