RazCrypto RazCrypto Docs
Dashboard Get API Keys
Docs Signature Verification

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

  1. Read the raw HTTP request body (before JSON parsing)
  2. Extract x-razcrypto-signature from request headers
  3. Generate HMAC-SHA256(rawBody, webhookSecret)
  4. 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']);
}
?>