Webhook API
Security

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

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', 200

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:

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

Product
Install AppWebsiteBook a Demo
Developers
JavaScript SDKREST API v2Webhook API
Company
Avada GroupHelp CenterContact
© 2026 Joy Loyalty by Avada Group. All rights reserved.