Tribhuvan University
Faculty of Management
Office of the Dean
Official Model Question Paper / Dean's Office Blueprint
Candidates are required to give their answers in their own words as far as practicable. The figures in the margin indicate full marks.
Group A
Brief Answer Questions. Attempt ALL questions.
[5 × 2 = 10]- [2]
Differentiate between Client-Side Scripting and Server-Side Scripting with two examples of each.
View model solution
Answer:
Parameter Client-Side Scripting Server-Side Scripting Execution Environment Executes inside the user’s web browser on the client device. Executes on the web/application server before sending HTML/JSON to client. Access to Resources Restricted sandbox; cannot directly connect to backend databases or server files. Full access to server filesystem, operating system, and database engines. Source Code Visibility Source code is publicly visible via browser ‘View Source’ or DevTools. Code remains hidden on the server; client receives only processed output. Examples JavaScript, TypeScript. PHP, Node.js (JavaScript), Python (Django), Java (Spring). - [2]
Compare HTTP GET and POST request methods with respect to data transmission, caching, and security.
View model solution
Answer:
Parameter HTTP GET Method HTTP POST Method Data Transmission Appends parameters directly to URL query string ( ?key=val).Transmits parameters inside HTTP request message body. Payload Size Limit Bounded by browser URL length limits (typically 2048 characters). Virtually unlimited payload size (suitable for file uploads). Browser Caching Cached and bookmarked by default; preserved in browser history. Never cached or bookmarked; not stored in browser history. Security Insecure for sensitive data (credentials visible in logs and URL). Secure for credentials; parameters encrypted over HTTPS connection. - [2]
What is a Web Session, and how does it differ from a Cookie? Where is session data stored?
View model solution
Answer:
- Cookie: A small key-value text file (up to 4 KB) stored locally on the client’s browser by the web server. Sent back with every subsequent HTTP request in the
Cookieheader. Vulnerable to client-side inspection and modification. - Session: A server-side state mechanism that persists user data across multiple page requests during a visit.
- Storage Location: Session data is physically stored on the web server (in temporary files, memory cache like Redis, or database). The client browser stores only a unique, opaque session identifier token (e.g.,
PHPSESSID) within a cookie.
- Cookie: A small key-value text file (up to 4 KB) stored locally on the client’s browser by the web server. Sent back with every subsequent HTTP request in the
- [2]
What is SQL Injection (SQLi)? Provide an example of a vulnerable SQL query and demonstrate how Prepared Statements prevent it.
View model solution
Answer:
- SQL Injection (SQLi): A critical vulnerability where an attacker injects malicious SQL statements into entry fields, tricking the backend database into executing unauthorized commands.
- Vulnerable Query (String Concatenation):
If attacker submits$sql = "SELECT * FROM users WHERE user = '" . $_POST['user'] . "' AND pass = '" . $_POST['pass'] . "'";' OR '1'='1, query becomes:SELECT * FROM users WHERE user = '' OR '1'='1' ..., bypassing login. - Prepared Statement Fix:
The database compiles the query structure before binding variables, treating input strictly as literal data rather than executable code.$stmt = $pdo->prepare("SELECT * FROM users WHERE user = :u AND pass = :p"); $stmt->execute(['u' => $_POST['user'], 'p' => $_POST['pass']]);
- [2]
What is AJAX? Explain the role of the modern
fetch()API in asynchronous client-server communication.View model solution
Answer:
- AJAX (Asynchronous JavaScript and XML): A web development technique enabling client-side web pages to asynchronously send and receive data from a backend server in the background without requiring a full page refresh.
- Role of
fetch()API: The standard browser interface replacing the legacyXMLHttpRequestobject. It provides a clean, promise-based API usingasync/awaitto dispatch HTTP requests, handle JSON payloads, manage HTTP headers, and stream responses asynchronously with robust error chaining.
Group B
Descriptive Answer Questions. Attempt any THREE questions.
[3 × 10 = 30]- [10]
Explain the Model-View-Controller (MVC) architectural pattern in server-side web engineering. Write a complete, secure PHP script using PDO (PHP Data Objects) that:
- Connects to a MySQL database with error mode set to exceptions.
- Accepts user registration input (
username,email,password) via POST. - Hashes the password securely using
password_hash()with BCRYPT. - Executes a prepared statement to insert the user and catches duplicate email constraint violations.
View model solution
MVC Architecture and Secure PHP PDO User Registration
1. The Model-View-Controller (MVC) Pattern
- Model: Encapsulates business logic, data structures, and database interactions (e.g., executing SQL queries, enforcing entity validation rules).
- View: Generates the user interface and presentation layout (HTML, JSON response) rendered to the client.
- Controller: Acts as an intermediary; receives incoming HTTP requests, processes input parameters, calls appropriate Model methods, and selects the View for response delivery.
2. Complete Secure PHP PDO Script (
register.php)<?php // Strict error reporting and JSON response header header('Content-Type: application/json; charset=UTF-8'); // Database Configuration $db_host = 'localhost'; $db_name = 'fom_portal'; $db_user = 'app_user'; $db_pass = 'StrongSecretPassword#2026'; // 1. Establish Secure PDO Connection $dsn = "mysql:host={$db_host};dbname={$db_name};charset=utf8mb4"; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, // Enforce real prepared statements ]; try { $pdo = new PDO($dsn, $db_user, $db_pass, $options); } catch (PDOException $e) { http_response_code(500); echo json_encode(['status' => 'error', 'message' => 'Database connection failed.']); exit; } // 2. Validate Request Method if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['status' => 'error', 'message' => 'Method Not Allowed. Use POST.']); exit; } // 3. Extract and Sanitize User Input $username = trim($_POST['username'] ?? ''); $email = trim($_POST['email'] ?? ''); $password = $_POST['password'] ?? ''; if (empty($username) || empty($email) || empty($password)) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'All fields are strictly required.']); exit; } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Invalid email address format.']); exit; } if (strlen($password) < 8) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Password must be at least 8 characters.']); exit; } // 4. Secure Password Hashing via BCRYPT $password_hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]); // 5. Insert Record using Parameterized Prepared Statement $sql = "INSERT INTO users (username, email, password_hash, created_at) VALUES (:username, :email, :password_hash, NOW())"; try { $stmt = $pdo->prepare($sql); $stmt->execute([ ':username' => $username, ':email' => $email, ':password_hash' => $password_hash ]); http_response_code(201); echo json_encode([ 'status' => 'success', 'message' => 'User registered successfully!', 'user_id' => $pdo->lastInsertId() ]); } catch (PDOException $e) { // 23000 is MySQL SQLSTATE for Integrity Constraint Violation (Duplicate Key) if ($e->getCode() == '23000') { http_response_code(409); // Conflict echo json_encode(['status' => 'error', 'message' => 'Email or username already registered.']); } else { http_response_code(500); echo json_encode(['status' => 'error', 'message' => 'Internal database error.']); } } ?>Security Defenses Implemented
PDO::ATTR_EMULATE_PREPARES => false: Prevents PDO from emulating prepared statements locally, delegating true parameterized execution to the MySQL server engine.password_hash(..., PASSWORD_BCRYPT): Applies adaptive salted hashing resistant to GPU rainbow table cracking.- Generic error messaging prevents leaking internal database schema names to clients.
- [10]
Analyze Web Authentication and Session Security: a) Detail the complete lifecycle of a web session: creation, cookie transmission, server-side persistence, and invalidation. (4 Marks) b) Explain Session Hijacking and Session Fixation attacks. Write complete PHP code demonstrating how to harden session initialization using
session_regenerate_id(),HttpOnly,Secure, andSameSitecookie flags, along with an idle timeout. (6 Marks)View model solution
Session Lifecycle and Hardened Authentication Security
a) Lifecycle of a Web Session
- Initiation: Client navigates to login page. Server initializes a new session via
session_start(), generating a cryptographically secure 128-bit random token (Session ID). - Cookie Transmission: Server sends the Session ID back to the client browser inside an HTTP response header:
Set-Cookie: PHPSESSID=d9a4f2...; Path=/; HttpOnly; Secure; SameSite=Strict - State Persistence: Server stores session variables (
$_SESSION['user_id'] = 42) in a server-side storage medium (RAM/Redis/file). On subsequent HTTP requests, the browser transmits the cookie in the request header (Cookie: PHPSESSID=d9a4f2...). - Invalidation: When the user logs out or times out, the server wipes
$_SESSION, callssession_destroy(), and expires the client cookie by setting its max-age to 0.
b) Session Attacks and Production Hardening Implementation
1. Attack Vectors
- Session Hijacking: An adversary intercepts a victim’s active Session ID via packet sniffing, malware, or XSS and impersonates the victim.
- Session Fixation: An adversary tricks a victim into authenticating using a pre-determined Session ID chosen by the attacker.
2. Hardened Session Manager Implementation in PHP
<?php // Configure Strict Cookie Security Settings BEFORE session_start() session_set_cookie_params([ 'lifetime' => 0, // Session cookie expires when browser closes 'path' => '/', 'domain' => '', 'secure' => true, // Enforce transmission strictly over HTTPS 'httponly' => true, // Block client-side JavaScript access (Defeats XSS session theft) 'samesite' => 'Strict' // Defeats Cross-Site Request Forgery (CSRF) ]); session_start(); // 1. Session Hijacking Defense: Bind Session to Client User-Agent Fingerprint $client_fingerprint = md5($_SERVER['HTTP_USER_AGENT'] ?? ''); if (!isset($_SESSION['fingerprint'])) { $_SESSION['fingerprint'] = $client_fingerprint; } else if ($_SESSION['fingerprint'] !== $client_fingerprint) { // Possible stolen session token used from a different browser destroy_session_and_abort("Session fingerprint mismatch. Possible hijacking detected."); } // 2. Idle Timeout Enforcement (15 Minutes = 900 Seconds) $max_idle_time = 900; if (isset($_SESSION['last_activity']) \&\& (time() - $_SESSION['last_activity'] > $max_idle_time)) { destroy_session_and_abort("Session expired due to inactivity. Please log in again."); } $_SESSION['last_activity'] = time(); // 3. Session Fixation Defense: Regenerate Session ID upon Privilege Escalation / Login function authenticate_user_login($user_id) { // Regenerate session ID and delete the old session file immediately session_regenerate_id(true); $_SESSION['authenticated'] = true; $_SESSION['user_id'] = $user_id; $_SESSION['login_time'] = time(); $_SESSION['last_activity'] = time(); } function destroy_session_and_abort($reason) { $_SESSION = []; if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } session_destroy(); http_response_code(401); echo json_encode(['error' => $reason]); exit; } ?> - Initiation: Client navigates to login page. Server initializes a new session via
- [10]
Analyze RESTful Web Services and API Architecture: a) Detail the core architectural constraints of REST: Statelessness, Client-Server separation, Cacheability, and Uniform Interface. (4 Marks) b) Implement a complete RESTful API controller in Node.js (Express) or PHP for an enterprise resource
/api/productssupporting full CRUD operations (GET all, GET by ID, POST create, and DELETE by ID) returning standard HTTP status codes (200,201,400,404,500) and JSON payloads. (6 Marks)View model solution
RESTful Web Services Architecture and Express.js API Implementation
a) Core Architectural Constraints of REST
- Statelessness: Every HTTP request from client to server must contain all information required to understand and process the request. The server never stores client session context between requests.
- Client-Server Separation: User interface concerns are decoupled from data storage concerns, allowing frontend mobile/web clients to evolve independently of backend microservices.
- Cacheability: Responses must explicitly declare themselves cacheable or non-cacheable (via
Cache-Controlheaders) to optimize network bandwidth. - Uniform Interface: Resources are identified by standard URIs (
/api/products), manipulated through representations (JSON/XML), and governed by standard HTTP verbs (GET, POST, PUT, DELETE).
b) Production RESTful API Implementation in Node.js / Express
const express = require('express'); const app = express(); app.use(express.json()); // Parse JSON request bodies // In-Memory Resource Store (Mock Database) let products = [ { id: 1, name: 'Nepali Orthodox Black Tea', price: 450, stock: 100 }, { id: 2, name: 'Mustang Organic Apple Juice', price: 280, stock: 50 } ]; let nextId = 3; // 1. GET /api/products - Retrieve all products app.get('/api/products', (req, res) => { res.status(200).json({ success: true, count: products.length, data: products }); }); // 2. GET /api/products/:id - Retrieve single product by ID app.get('/api/products/:id', (req, res) => { const id = parseInt(req.params.id, 10); const product = products.find(p => p.id === id); if (!product) { return res.status(404).json({ success: false, error: `Product with ID ${id} not found.` }); } res.status(200).json({ success: true, data: product }); }); // 3. POST /api/products - Create a new product app.post('/api/products', (req, res) => { const { name, price, stock } = req.body; // Input Validation if (!name || typeof price !== 'number' || typeof stock !== 'number' || price <= 0 || stock < 0) { return res.status(400).json({ success: false, error: 'Invalid payload. "name" (string), "price" (number > 0), and "stock" (>= 0) are required.' }); } const newProduct = { id: nextId++, name: name.trim(), price, stock }; products.push(newProduct); // 201 Created with resource representation res.status(201).json({ success: true, data: newProduct }); }); // 4. DELETE /api/products/:id - Remove product by ID app.delete('/api/products/:id', (req, res) => { const id = parseInt(req.params.id, 10); const index = products.findIndex(p => p.id === id); if (index === -1) { return res.status(404).json({ success: false, error: `Product with ID ${id} does not exist.` }); } const removed = products.splice(index, 1)[0]; res.status(200).json({ success: true, message: `Product ${id} deleted successfully.`, data: removed }); }); // Start Server const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`REST API Server running on port ${PORT}`)); - [10]
Examine Asynchronous Client-Server Communication: a) Contrast traditional synchronous HTTP page requests, AJAX polling, and WebSockets in terms of connection persistence, overhead, and real-time bidirectional capability. (4 Marks) b) Write a complete client-side JavaScript script using
async/awaitandfetch()to submit a JSON payload to a server API endpoint. The script must show a loading spinner, handle network errors (HTTP 4xx/5xx), and dynamically render success confirmation or error messages to the DOM. (6 Marks)View model solution
Asynchronous Web Communication: AJAX, WebSockets, and Modern Fetch
a) Comparison of Web Communication Protocols
Feature Traditional Synchronous HTTP AJAX (Short / Long Polling) WebSockets (RFC 6455) Connection Model Request-Response: connection closes after each full page load. Repeated independent HTTP request-response cycles. Single persistent, full-duplex TCP socket connection. Real-Time Latency High; requires full page reload to view changes. Moderate; updates delayed by polling interval (e.g., 5s). Sub-millisecond; server pushes data instantly to client. Header Overhead High; full HTTP headers sent on every interaction. High; repetitive HTTP cookie/auth headers sent per poll. Extremely Low; only 2 to 10 bytes frame header per message. Directionality Unidirectional (Client-initiated only). Unidirectional (Client pulls). Bidirectional; client and server send messages concurrently.
b) Production Asynchronous Fetch Implementation in JavaScript
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Async Fetch Feedback</title> <style> .spinner { display: none; width: 24px; height: 24px; border: 3px solid #ccc; border-top-color: #2563eb; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .alert-success { background: #dcfce7; color: #166534; padding: 10px; border-radius: 6px; margin-top: 12px; } .alert-error { background: #fee2e2; color: #991b1b; padding: 10px; border-radius: 6px; margin-top: 12px; } </style> </head> <body> <form id="feedback-form"> <textarea id="feedback-text" placeholder="Enter course feedback..." required></textarea> <button type="submit" id="submit-btn">Submit Feedback</button> <div id="spinner" class="spinner"></div> </form> <div id="response-msg" role="alert"></div> <script> const form = document.getElementById('feedback-form'); const submitBtn = document.getElementById('submit-btn'); const spinner = document.getElementById('spinner'); const responseMsg = document.getElementById('response-msg'); form.addEventListener('submit', async (e) => { e.preventDefault(); const payload = { courseCode: 'IT 219', feedback: document.getElementById('feedback-text').value.trim() }; // 1. Enter Loading State submitBtn.disabled = true; spinner.style.display = 'inline-block'; responseMsg.innerHTML = ''; try { // 2. Dispatch Async Fetch Request const response = await fetch('/api/feedback', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload) }); const data = await response.json(); // 3. Inspect HTTP Status if (!response.ok) { // Handles 4xx or 5xx HTTP response codes throw new Error(data.message || `Server responded with status ${response.status}`); } // 4. Render Success responseMsg.className = 'alert-success'; responseMsg.textContent = 'Feedback successfully recorded! Thank you.'; form.reset(); } catch (err) { // 5. Handle Network Failures & API Errors responseMsg.className = 'alert-error'; responseMsg.textContent = `Submission Failed: ${err.message}`; } finally { // 6. Reset UI State submitBtn.disabled = false; spinner.style.display = 'none'; } }); </script> </body> </html>
Group C
Comprehensive Answer / Case Analysis Question. Attempt ALL questions.
[1 × 20 = 20]- [20]
Full-Stack Web Architecture Case Study: Enterprise Multi-Vendor E-Commerce Platform & Payment Webhook Engine
A nationwide retail aggregator in Nepal is deploying an e-commerce platform connecting 500 local vendors to 200,000 online shoppers during seasonal flash sale events:
- Concurrency & Inventory Challenges: High-demand flash sales (e.g. 50 discounted smartphones) attract thousands of concurrent buyers clicking ‘Buy Now’ simultaneously. Naive database updates cause race conditions, resulting in overselling inventory.
- Transactional Integrity: Order placement requires an ACID-compliant transaction: checking inventory stock, decrementing available stock, creating an order record, and generating customer invoice items. If any step fails, the entire transaction must abort cleanly.
- Payment Gateway Webhook Verification: The platform integrates with digital wallets (eSewa / Khalti). When a payment finishes, the payment gateway issues an asynchronous server-to-server HTTP POST webhook callback containing transaction IDs and cryptographic HMAC-SHA256 signatures to confirm payment before marking orders as ‘Paid’.
Questions: a) Formulate the multi-tier system architecture. Draw the architectural diagram linking the Client SPA, Nginx Reverse Proxy, Application Server Cluster (Node.js/PHP), Redis Cache, and MySQL Primary-Replica database. Design the REST API endpoint contract for checkout. (6 Marks) b) Implement ACID transactional inventory deduction and order creation in PHP/Node.js using SQL row-level locking: Write complete, production code utilizing
START TRANSACTION,SELECT stock FROM products WHERE id = ? FOR UPDATE(pessimistic locking), deducting stock, inserting order records, and executingCOMMITorROLLBACKon failure. (7 Marks) c) Engineer API security and cryptographic webhook verification: Detail JSON Web Token (JWT) stateless authentication with refresh token rotation, and implement HMAC-SHA256 signature verification in PHP/Node.js to authenticate eSewa/Khalti payment gateway webhook callbacks, defending against replay attacks and forged transactions. (7 Marks)View model solution
Full-Stack Architecture Solution: Enterprise Multi-Vendor E-Commerce Platform
a) Multi-Tier System Architecture & REST Endpoint Contracts
1. System Architecture Blueprint
[Client Web / Mobile SPA] | HTTPS (Port 443) v [Nginx Reverse Proxy & Load Balancer] | +----+----+ (Round-Robin Load Balancing) | | [App Node 1] [App Node 2] <---> [Redis In-Memory Cluster] | | (Session Store & Fast Product Cache) +----+----+ | +---> [MySQL Primary (Write Master)] ---> [MySQL Read Replica 1] ---> [MySQL Read Replica 2]- Nginx Reverse Proxy: Terminates SSL/TLS, buffers slow clients, and enforces rate-limiting (preventing DDoS).
- Application Cluster: Stateless Node.js / PHP workers serving API requests.
- Redis Cache: Caches hot read-only catalog data; reduces database read load by 85%.
- MySQL Primary-Replica: Master database executes transactional writes; read replicas serve catalog queries.
2. REST API Checkout Contract (
POST /api/orders/checkout)- Headers:
Authorization: Bearer <JWT_ACCESS_TOKEN>,Content-Type: application/json - Request Body:
{ "items": [{ "productId": 108, "quantity": 2 }], "shippingAddress": "New Road, Kathmandu", "paymentMethod": "ESEWA" } - Response (201 Created):
{ "success": true, "orderId": "ORD-2026-9941", "totalAmount": 48000, "paymentUrl": "https://esewa.com.np/epay/main?pid=ORD-2026-9941..." }
b) ACID Transactional Checkout with Pessimistic Row-Level Locking
<?php function process_checkout_transaction(PDO $pdo, $userId, $productId, $quantityRequested, $shippingAddress) { try { // 1. Begin ACID Transaction $pdo->beginTransaction(); // 2. Pessimistic Row Lock using 'FOR UPDATE' // This halts concurrent transactions until the current transaction commits or rolls back! $stmt = $pdo->prepare("SELECT stock, price, vendor_id FROM products WHERE id = :id FOR UPDATE"); $stmt->execute([':id' => $productId]); $product = $stmt->fetch(); if (!$product) { $pdo->rollBack(); return ['success' => false, 'code' => 404, 'error' => 'Product not found.']; } // 3. Strict Stock Validation under Exclusive Lock if ($product['stock'] < $quantityRequested) { $pdo->rollBack(); // Release lock immediately return ['success' => false, 'code' => 409, 'error' => 'Insufficient stock for flash sale item.']; } // 4. Deduct Inventory Stock $updateStock = $pdo->prepare("UPDATE products SET stock = stock - :qty WHERE id = :id"); $updateStock->execute([':qty' => $quantityRequested, ':id' => $productId]); // 5. Create Master Order Record $totalAmount = $product['price'] * $quantityRequested; $createOrder = $pdo->prepare("INSERT INTO orders (user_id, total_amount, shipping_address, status, created_at) VALUES (:uid, :amount, :addr, 'PENDING_PAYMENT', NOW())"); $createOrder->execute([ ':uid' => $userId, ':amount' => $totalAmount, ':addr' => $shippingAddress ]); $orderId = $pdo->lastInsertId(); // 6. Create Order Item Line $createItem = $pdo->prepare("INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (:oid, :pid, :qty, :price)"); $createItem->execute([ ':oid' => $orderId, ':pid' => $productId, ':qty' => $quantityRequested, ':price' => $product['price'] ]); // 7. Commit Transaction (Releases Row Lock atomically) $pdo->commit(); return [ 'success' => true, 'code' => 201, 'orderId' => $orderId, 'totalAmount' => $totalAmount ]; } catch (Exception $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } return ['success' => false, 'code' => 500, 'error' => 'Transaction failed: ' . $e->getMessage()]; } } ?>
c) Payment Webhook Verification and Cryptographic Security
1. HMAC-SHA256 Signature Verification Script (
webhook_esewa.php)When a digital wallet (eSewa/Khalti) notifies the server of a successful customer payment, attackers could forge callbacks. HMAC-SHA256 guarantees authenticity:
<?php $webhook_secret_key = getenv('PAYMENT_WEBHOOK_SECRET'); // Secure environment variable // 1. Read Raw Incoming Payload and Signature Header $raw_payload = file_get_contents('php://input'); $received_signature = $_SERVER['HTTP_X_SIGNATURE'] ?? ''; $received_timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? 0; // 2. Defend Against Replay Attacks (Timestamp freshness check within 300 seconds) if (abs(time() - (int)$received_timestamp) > 300) { http_response_code(400); echo json_encode(['error' => 'Webhook timestamp expired. Possible replay attack.']); exit; } // 3. Compute Expected HMAC-SHA256 Signature $signed_data = $received_timestamp . '.' . $raw_payload; $expected_signature = hash_hmac('sha256', $signed_data, $webhook_secret_key); // 4. Constant-Time Signature Comparison (Prevents Timing Attacks) if (!hash_equals($expected_signature, $received_signature)) { http_response_code(403); echo json_encode(['error' => 'Invalid cryptographic signature. Forged webhook detected.']); exit; } // 5. Signature Verified: Parse Payload and Update Order Status $event = json_decode($raw_payload, true); $orderId = $event['order_id']; $paymentStatus = $event['status']; // e.g. 'COMPLETED' if ($paymentStatus === 'COMPLETED') { // Update order status in DB to 'PAID' $stmt = $pdo->prepare("UPDATE orders SET status = 'PAID', paid_at = NOW() WHERE id = :id"); $stmt->execute([':id' => $orderId]); } http_response_code(200); echo json_encode(['status' => 'acknowledged']); ?>