Webhook Signature Verification
Always verify webhook signatures to ensure requests are genuinely from RazCrypto.
Security Critical: Never process webhook data without verifying the signature first. Always use the raw, unparsed request body for HMAC calculation.
How Verification Works
- Read the raw HTTP request body (before JSON parsing)
- Extract
x-razcrypto-signaturefrom request headers - Generate
HMAC-SHA256(rawBody, webhookSecret) - Compare your hash with the received signature using constant-time comparison
Where to find Webhook Secret: Dashboard → Platforms → Webhook Secret column
Verification Examples
<?php
$rawPayload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RAZCRYPTO_SIGNATURE'] ?? '';
$secret = getenv('RAZ_WEBHOOK_SECRET');
$expected = hash_hmac('sha256', $rawPayload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit(json_encode(['error' => 'Invalid signature']));
}
// Safe to process
$data = json_decode($rawPayload, true);
?>
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString('utf8');
const signature = req.headers['x-razcrypto-signature'] || '';
const secret = process.env.RAZ_WEBHOOK_SECRET;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signature)
)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const data = JSON.parse(payload);
// Process payment event...
res.status(200).json({ status: 'ok' });
});
import hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('RAZ_WEBHOOK_SECRET', '')
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.get_data(as_text=True)
signature = request.headers.get('X-Razcrypto-Signature', '')
expected = hmac.new(
WEBHOOK_SECRET.encode(), payload.encode(), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
return jsonify({'error': 'Invalid signature'}), 401
data = request.get_json(force=True)
# Process event...
return jsonify({'status': 'ok'})
<?php
// In your Laravel webhook controller
public function handle(Request $request)
{
$rawPayload = $request->getContent();
$signature = $request->header('x-razcrypto-signature', '');
$secret = config('services.razcrypto.webhook_secret');
$expected = hash_hmac('sha256', $rawPayload, $secret);
if (!hash_equals($expected, $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
$data = $request->json()->all();
$event = $data['event'] ?? '';
if ($event === 'payment.completed') {
// Update your order/invoice
}
return response()->json(['status' => 'success']);
}
?>