X-Payracle-Signature header — an HMAC-SHA256 signature of the raw JSON request body, signed with your secret key. Always verify this header before processing the event.
X-Payracle-Signature: sha256=3b5d9e2f1a...
Compute the signature over the raw request body bytes, before any JSON parsing. Re-serializing a parsed object rarely produces byte-identical output (key order, whitespace), which will make a genuine webhook fail verification.
const crypto = require('crypto');
app.post('/webhook/payracle', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-payracle-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.PAYRACLE_SECRET_KEY)
.update(req.body) // raw Buffer — do NOT use req.body after JSON.parse
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// handle event...
res.sendStatus(200);
});
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhook/payracle', methods=['POST'])
def webhook():
signature = request.headers.get('X-Payracle-Signature', '')
expected = 'sha256=' + hmac.new(
os.environ['PAYRACLE_SECRET_KEY'].encode(),
request.get_data(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401)
event = request.get_json()
# handle event...
return '', 200
<?php
// PHP / Laravel
$signature = $request->header('X-Payracle-Signature');
$expected = 'sha256=' . hash_hmac('sha256', $request->getContent(), config('services.payracle.secret_key'));
if (!hash_equals($expected, $signature)) {
abort(401, 'Invalid signature');
}
$event = $request->json()->all();
// handle event...