# Webhook – Payment Event Notifications ## Overview * Webhooks are used to notify partners about **payment-related events** in real time. * When a partner (for example, a UK-based partner) initiates a payment through **Theropay**, the payment is processed by the Banking Provider. * Once the Banking Provider updates the payment status, Theropay receives this update and **forwards the relevant information to the partner via a webhook**. * This allows partners to stay informed about payment progress without continuously polling APIs. ## High-Level Flow 1. Partner initiates a payment using Theropay APIs. 2. Theropay sends the payment to the Banking Provider for processing. 3. The Banking Provider sends a webhook event to Theropay when the payment status changes. 4. Theropay validates and processes the webhook. 5. Theropay sends a webhook notification to the partner’s configured webhook URL. ## Security & Signature Verification To ensure webhook authenticity, every incoming webhook request is validated using **HMAC SHA-256 signature verification**. ### Required Headers | Header Name | Description | | --- | --- | | `X-Security-Digest` | HMAC SHA-256 signature of the request | | `X-Original-Transmission-Time` | Original timestamp of the webhook event | ### How Signature Verification Works 1. Theropay reads the raw request body. 2. The original transmission timestamp is prepended to the request body. 3. An HMAC SHA-256 hash is generated using a shared secret key. 4. The generated hash is compared with the value sent in `X-Security-Digest`. If the signature does not match, the webhook request is rejected. ## Example Verification Code (C#) ```csharp [HttpPost("ReceiveWebhook")] public async Task ReceiveWebhook() { var requestBody = await new StreamReader(Request.Body).ReadToEndAsync(); var theropaySignature = Request.Headers["X-Theropay-Signature"].FirstOrDefault(); var theropayTimestamp = Request.Headers["X-Theropay-Timestamp"].FirstOrDefault(); var partnerSecret = "OIviKHaZVXcHg9QB0j8Dzq/1bf4y7CgKmIu9iQktMJ4="; // Base64 from Theropay on webhook creation var isValid = VerifySignature( requestBody, theropaySignature, theropayTimestamp, partnerSecret); if (!isValid) { return Unauthorized("Invalid webhook signature."); } // Process webhook event var json = JsonSerializer.Deserialize(requestBody); return Ok(); } public static bool VerifySignature(string requestBody, string theropaySignatureHeader, string theropayTimestamp, string partnerSecretPlainText) { // remove sha256= var expectedSignatureHex = theropaySignatureHeader.StartsWith("sha256=") ? theropaySignatureHeader.Substring(7) : theropaySignatureHeader; // DO NOT Base64 decode — match sender logic var secretBytes = Encoding.UTF8.GetBytes(partnerSecretPlainText); var signingString = $"{theropayTimestamp}.{requestBody}"; var signingBytes = Encoding.UTF8.GetBytes(signingString); using var hmac = new HMACSHA256(secretBytes); var hash = hmac.ComputeHash(signingBytes); var computedHex = Convert.ToHexString(hash).ToLower(); return SlowEquals(computedHex, expectedSignatureHex); } private static bool SlowEquals(string a, string b) { // Constant-time comparison to avoid timing attacks var aBytes = Encoding.UTF8.GetBytes(a); var bBytes = Encoding.UTF8.GetBytes(b); if (aBytes.Length != bBytes.Length) return false; int diff = 0; for (int i = 0; i < aBytes.Length; i++) diff |= aBytes[i] ^ bBytes[i]; return diff == 0; } ``` ## Webhook Event Structure Every webhook request contains the following top-level fields: ```json { "eventType": "TRANSACTION_STATUS_UPDATE", "payload": { } } ``` | Field | Description | | --- | --- | | `eventType` | Identifies the type of event being sent | | `payload` | Contains event-specific data | ## Summary Webhooks provide a reliable and secure way to receive real-time payment updates. By validating signatures and handling events correctly, partners can seamlessly track payment lifecycles without additional API calls.