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
- Get the raw body of the incoming webhook request
- Calculate HMAC-SHA256 using your shop's secret key
- Compare the calculated hash with the header value
Verification examples
Node.js
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
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', 200PHP
<?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:
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