# Security

## HMAC Verification

To ensure webhooks are authentically from Joy Loyalty, each webhook request includes an `X-Joy-Loyalty-Hmac-Sha256` header containing the HMAC-SHA256 signature.

### Verification process

1. Get the raw body of the incoming webhook request
2. Calculate HMAC-SHA256 using your shop's secret key
3. Compare the calculated hash with the header value

### Verification examples

#### Node.js

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

function verifyWebhook(rawBody, hmacHeader, secretKey) {
  const calculatedHmac = crypto
    .createHmac('sha256', secretKey)
    .update(rawBody, 'utf8')
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(calculatedHmac),
    Buffer.from(hmacHeader)
  );
}

// Express middleware
app.post('/webhook/points', express.raw({type: 'application/json'}), (req, res) => {
  const hmac = req.get('X-Joy-Loyalty-Hmac-Sha256');
  const isValid = verifyWebhook(req.body, hmac, process.env.JOY_SECRET_KEY);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body);
  // Process webhook...

  res.status(200).send('OK');
});
```

#### Python

```python
import hmac
import hashlib
import base64

def verify_webhook(raw_body, hmac_header, secret_key):
    calculated_hmac = base64.b64encode(
        hmac.new(
            secret_key.encode('utf-8'),
            raw_body,
            hashlib.sha256
        ).digest()
    ).decode()
    
    return hmac.compare_digest(calculated_hmac, hmac_header)

# Flask example
@app.route('/webhook/points', methods=['POST'])
def handle_webhook():
    hmac_header = request.headers.get('X-Joy-Loyalty-Hmac-Sha256')
    if not verify_webhook(request.data, hmac_header, SECRET_KEY):
        return 'Invalid signature', 401
    
    payload = request.get_json()
    # Process webhook...
    
    return 'OK', 200
```

#### PHP

```php
<?php
function verifyWebhook($rawBody, $hmacHeader, $secretKey) {
    $calculatedHmac = base64_encode(hash_hmac('sha256', $rawBody, $secretKey, true));
    return hash_equals($calculatedHmac, $hmacHeader);
}

// Usage
$rawBody = file_get_contents('php://input');
$hmacHeader = $_SERVER['HTTP_X_JOY_LOYALTY_HMAC_SHA256'] ?? '';

if (!verifyWebhook($rawBody, $hmacHeader, $secretKey)) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($rawBody, true);
// Process webhook...

http_response_code(200);
echo 'OK';
?>
```

## Security considerations

### HMAC verification best practices

* **Always verify** HMAC signatures to ensure webhook authenticity
* **Use timing-safe comparison** functions to prevent timing attacks
* **Store secret keys securely** using environment variables or secure key management

### HTTPS requirements

* **Webhook endpoints must use HTTPS** for secure data transmission
* **Use valid SSL certificates** - self-signed certificates are not supported
* **Consider certificate pinning** for additional security

### IP whitelisting

Consider implementing IP whitelisting for additional security:

```javascript
const allowedIPs = ['webhook-ip-range']; // Update with actual IP ranges

app.use('/webhook', (req, res, next) => {
  const clientIP = req.ip || req.connection.remoteAddress;
  
  if (!allowedIPs.includes(clientIP)) {
    return res.status(403).send('Forbidden');
  }
  
  next();
});
```

### Data privacy

* **Webhook payloads contain customer PII** including emails and names
* **Ensure GDPR/CCPA compliance** in your data handling
* **Implement proper access controls** and audit logging
* **Consider data retention policies** for webhook data


---

# Agent Instructions: 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:

```
GET https://devdocs.joy.so/webhook-api/security.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
