← Blog/Developer

How to Properly Verify Bekena Webhooks (With Code Examples)

Apr 18, 2026 · 4 min read

Never process a payment without verifying the webhook signature. This guide shows exactly how to do HMAC-SHA256 verification in Node.js, Python, PHP, and Go.

Every webhook Bekena sends includes an X-Bekena-Sig header: an HMAC-SHA256 signature computed from the raw request body using your webhook secret. Verifying this signature before processing any payment event is not optional — it's what prevents an attacker from sending fake payment.confirmed events to your endpoint and fraudulently getting your products delivered.

How the Signature Works

Bekena computes: HMAC-SHA256(rawBody, webhookSecret) and sends the hex digest in X-Bekena-Sig. Your code must compute the same HMAC using your webhook secret (from the Bekena dashboard) and compare it to the header. If they match, the webhook is authentic.

Note: Critical: Use the raw request body bytes for HMAC computation, not a parsed JSON object. Parsing changes whitespace and key ordering, producing a different hash.

Node.js / TypeScript

typescript
import crypto from 'crypto'; function verifyWebhook(rawBody: Buffer, signature: string, secret: string): boolean { const expected = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); // Use timingSafeEqual to prevent timing attacks return crypto.timingSafeEqual( Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex') ); } // Express middleware app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['x-bekena-sig'] as string; if (!verifyWebhook(req.body, sig, process.env.BEKENA_WEBHOOK_SECRET!)) { return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body.toString()); // process event... res.json({ received: true }); });

Python

python
import hmac import hashlib def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode('utf-8'), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) # Flask example @app.route('/webhook', methods=['POST']) def webhook(): sig = request.headers.get('X-Bekena-Sig', '') if not verify_webhook(request.get_data(), sig, WEBHOOK_SECRET): return jsonify(error='Invalid signature'), 401 event = request.get_json() # process event... return jsonify(received=True)

PHP

php
function verifyWebhook(string $rawBody, string $signature, string $secret): bool { $expected = hash_hmac('sha256', $rawBody, $secret); return hash_equals($expected, $signature); } $rawBody = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_BEKENA_SIG'] ?? ''; if (!verifyWebhook($rawBody, $signature, $_ENV['BEKENA_WEBHOOK_SECRET'])) { http_response_code(401); exit('Invalid signature'); } $event = json_decode($rawBody, true); // process $event...

Common Mistakes to Avoid

  • ✓Using string comparison (==) instead of constant-time comparison — always use timingSafeEqual or equivalent
  • ✓Parsing JSON before computing HMAC — always use the raw bytes
  • ✓Not checking the webhook secret is set in your environment — fail loudly if missing
  • ✓Ignoring the event type — check event.type before processing
  • ✓Not returning 200 immediately — process webhooks async and respond fast to prevent retries

Idempotency: Handle Duplicate Deliveries

Bekena retries failed webhook deliveries up to 10 times with exponential backoff. Your handler must be idempotent — processing the same payment.confirmed event twice should be safe. Use the event.data.order_id as an idempotency key and skip processing if the order is already marked as paid.

Ready to accept crypto payments?

Start with a free sandbox account

Create Free Account →

Related articles

Developer

Understanding Idempotency Keys in Payment APIs

Tutorial

How to Add Crypto Checkout to WooCommerce in 15 Minutes

Developer

Building a Telegram Bot That Accepts Crypto Payments