Webhooks
Security
To ensure the security and authenticity of webhook deliveries, we sign all webhook payloads using HMAC SHA-256. This allows you to verify that webhooks are genuinely sent from our service and haven't been tampered with during transmission.
Signature Verification
Each webhook request includes a signature in the X-UserCheck-Signature
header. Your webhook secret is provided when you create a webhook endpoint in our dashboard.
Important: Keep your webhook secret secure and never share it.
Verification Process
- Extract the signature from the
X-UserCheck-Signature
HTTP header - Generate a new signature using the same method (HMAC SHA-256 with your secret)
- Compare the signatures using constant-time comparison to prevent timing attacks
Implementation Examples
PHP Example
<?php
// Get the payload and signature from the webhook request
$payload = json_decode(file_get_contents('php://input'), true);
$signature = $_SERVER['HTTP_X_USERCHECK_SIGNATURE'] ?? '';
$secret = 'your_webhook_secret';
// Generate the expected signature
function generateSignature(array $payload, string $secret): string {
$payloadJson = json_encode($payload);
return hash_hmac('sha256', $payloadJson, $secret);
}
// Verify using constant-time comparison
function verifySignature(array $payload, string $signature, string $secret): bool {
$expectedSignature = generateSignature($payload, $secret);
return hash_equals($expectedSignature, $signature);
}
// Check if the signature is valid
if (verifySignature($payload, $signature, $secret)) {
// Process the webhook
$event = $payload['event'];
$domain = $payload['data']['domain'];
switch ($event) {
case 'domain.flagged.disposable':
// Handle disposable domain flagging
break;
case 'domain.flagged.relay':
// Handle relay domain flagging
break;
case 'domain.flagged.spam':
// Handle spam domain flagging
break;
}
http_response_code(200);
echo json_encode(['status' => 'success']);
} else {
// Invalid signature
http_response_code(401);
echo json_encode(['status' => 'error', 'message' => 'Invalid signature']);
}
?>
Node.js Example
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const payload = req.body;
const signature = req.headers['x-usercheck-signature'] || '';
const secret = 'your_webhook_secret';
function generateSignature(payload, secret) {
const payloadString = JSON.stringify(payload);
return crypto.createHmac('sha256', secret)
.update(payloadString)
.digest('hex');
}
function verifySignature(payload, signature, secret) {
const expectedSignature = generateSignature(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(signature, 'hex')
);
}
try {
if (verifySignature(payload, signature, secret)) {
// Process the webhook
const { event, data } = payload;
const { domain } = data;
switch (event) {
case 'domain.flagged.disposable':
// Handle disposable domain flagging
break;
case 'domain.flagged.relay':
// Handle relay domain flagging
break;
case 'domain.flagged.spam':
// Handle spam domain flagging
break;
}
res.status(200).json({ status: 'success' });
} else {
res.status(401).json({ status: 'error', message: 'Invalid signature' });
}
} catch (error) {
res.status(400).json({ status: 'error', message: 'Verification failed' });
}
});
Python Example
import json
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook_handler():
payload = request.json
signature = request.headers.get('X-UserCheck-Signature', '')
secret = 'your_webhook_secret'
def generate_signature(payload, secret):
payload_json = json.dumps(payload)
return hmac.new(
secret.encode('utf-8'),
payload_json.encode('utf-8'),
hashlib.sha256
).hexdigest()
def verify_signature(payload, signature, secret):
expected_signature = generate_signature(payload, secret)
return hmac.compare_digest(expected_signature, signature)
if verify_signature(payload, signature, secret):
# Process the webhook
event = payload['event']
domain = payload['data']['domain']
if event == 'domain.flagged.disposable':
# Handle disposable domain flagging
pass
elif event == 'domain.flagged.relay':
# Handle relay domain flagging
pass
elif event == 'domain.flagged.spam':
# Handle spam domain flagging
pass
return jsonify({'status': 'success'}), 200
else:
return jsonify({'status': 'error', 'message': 'Invalid signature'}), 401
if __name__ == '__main__':
app.run(debug=True)