Verifying signatures
Always verify X-Zyvo-Signature before trusting a webhook.
Compute the HMAC over the raw request body (before JSON parsing) with your webhook secret and compare in constant time.
// Laravel
Route::post('/zyvopay/webhook', function (Request $request) {
$expected = hash_hmac('sha256', $request->getContent(), config('services.zyvopay.webhook_secret'));
abort_unless(hash_equals($expected, (string) $request->header('X-Zyvo-Signature')), 401);
// …
return response()->noContent();
});
// Express
app.post('/zyvopay/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.ZYVO_WEBHOOK_SECRET).update(req.body).digest('hex');
const given = req.get('X-Zyvo-Signature') || '';
if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) return res.sendStatus(401);
res.sendStatus(200);
});
# Flask
@app.post('/zyvopay/webhook')
def webhook():
expected = hmac.new(SECRET.encode(), request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get('X-Zyvo-Signature', '')):
abort(401)
return '', 200
Treat webhooks as notifications: for high-value orders you can additionally fetch GET /v1/invoices/{uuid} and check status before fulfilling.