> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loopreturns.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Validate webhooks from Loop

Each webhook sent by Loop includes an `X-Loop-Signature` header.

This header contains a value you can use to verify that the webhook originated from Loop’s system. The value is generated using the SHA256 algorithm.

When you receive a webhook from Loop, use the webhook’s secret to generate a SHA256 HMAC of the request body, encoded as a base64 value. You can find the webhook secret in the [Developer Tools](https://admin.loopreturns.com/settings/developers) page of the Loop Admin (the same place you can create webhook subscriptions).

Compare your calculated HMAC to the value in the X-Loop-Signature header included with the request. If the values match, you can confirm that the webhook came from Loop and has not been tampered with.

## Code Samples

<CodeGroup>
  ```javascript Javascript theme={null}
  const assert = require('assert');
  const crypto = require('crypto');
  const express = require('express');

  const app = express();

  app.use(express.raw({
      type: '*/*',
      verify: function(req, _res, buf) {
          if (Buffer.isBuffer(buf)) {
              req.buffer = buf;
          }
      },
  }));
  app.use(express.json())

  app.post('/endpoint', (req, res) => {
    const jsonPayload = JSON.stringify(req.body);

    const hmac = crypto.createHmac('sha256', '<SECRET>') // substitute secret
          .update(req.buffer, 'utf-8')
          .digest('base64');

    const signature = req.get('X-Loop-Signature');

    assert.equal(hmac, signature); // validate match

    res.json({ signature, jsonPayload });
  });

  app.listen(3000, () => console.log('ready'));
  ```

  ```php PHP theme={null}
  <?php

  define('SECRET', '<SECRET>'); // Substitute secret

  header("Content-Type: application/json");

  $body = file_get_contents('php://input');
  $signature = $_SERVER['HTTP_X_LOOP_SIGNATURE'] ?? '';

  // Generate HMAC
  $calculated_hmac = base64_encode(hash_hmac('sha256', $body, SECRET, true));

  // Validate signature
  if ($calculated_hmac !== $signature) {
      http_response_code(401);
      echo json_encode(["error" => "Invalid signature"]);
      exit();
  }

  // Decode JSON payload
  $jsonPayload = json_decode($body, true);

  echo json_encode([
      "signature" => $signature,
      "jsonPayload" => $jsonPayload
  ]);
  ```

  ```ruby Ruby theme={null}
  require 'sinatra'
  require 'openssl'
  require 'base64'
  require 'json'

  SECRET = "<SECRET>"  # Substitute secret

  post '/endpoint' do
    request.body.rewind
    raw_body = request.body.read
    signature = request.env['HTTP_X_LOOP_SIGNATURE']

    # Calculate HMAC using SHA256
    hmac = OpenSSL::HMAC.digest('sha256', SECRET, raw_body)
    calculated_hmac = Base64.encode64(hmac).strip

    # Verify the signature
    if calculated_hmac != signature
      status 401
      return { error: 'Invalid signature' }.to_json
    end

    # Parse the JSON payload
    json_payload = JSON.parse(raw_body)

    # Return response
    content_type :json
    { signature: signature, jsonPayload: json_payload }.to_json
  end
  ```

  ```python Python theme={null}
  from flask import Flask, request, jsonify
  import hmac
  import hashlib
  import base64

  app = Flask(__name__)

  SECRET = b"<SECRET>"  # Substitute secret

  @app.route('/endpoint', methods=['POST'])
  def webhook():
      raw_body = request.data  # Get raw request body
      signature = request.headers.get('X-Loop-Signature')

      # Calculate HMAC
      calculated_hmac = base64.b64encode(
          hmac.new(SECRET, raw_body, hashlib.sha256).digest()
      ).decode()

      # Validate signature
      if calculated_hmac != signature:
          return "Invalid signature", 401

      # Process payload
      json_payload = request.json
      return jsonify({"signature": signature, "jsonPayload": json_payload})

  if __name__ == '__main__':
      app.run(port=3000)
  ```

  ```java Java theme={null}
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;
  import org.springframework.web.bind.annotation.*;
  import org.springframework.http.ResponseEntity;

  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.util.Base64;

  @SpringBootApplication
  public class WebhookVerificationApp {

      public static void main(String[] args) {
          SpringApplication.run(WebhookVerificationApp.class, args);
      }
  }

  @RestController
  @RequestMapping("/endpoint")
  class WebhookController {

      private static final String SECRET = "<SECRET>"; // Substitute secret

      @PostMapping
      public ResponseEntity<Object> handleWebhook(@RequestBody byte[] body, @RequestHeader("X-Loop-Signature") String signature) {
          try {
              // Generate HMAC using SHA256 and the provided secret
              Mac hmacSha256 = Mac.getInstance("HmacSHA256");
              SecretKeySpec secretKey = new SecretKeySpec(SECRET.getBytes(), "HmacSHA256");
              hmacSha256.init(secretKey);

              byte[] hmacBytes = hmacSha256.doFinal(body);
              String calculatedHmac = Base64.getEncoder().encodeToString(hmacBytes);

              // Compare the calculated HMAC with the signature
              if (!calculatedHmac.equals(signature)) {
                  return ResponseEntity.status(401).body("Invalid signature");
              }

              // Convert byte array payload to JSON string
              String jsonPayload = new String(body);

              return ResponseEntity.ok(String.format("Signature: %s, Payload: %s", signature, jsonPayload));
          } catch (Exception e) {
              return ResponseEntity.status(500).body("Error verifying webhook");
          }
      }
  }
  ```

  ```csharp C# theme={null}
  using Microsoft.AspNetCore.Mvc;
  using System.Security.Cryptography;
  using System.Text;

  var builder = WebApplication.CreateBuilder(args);
  var app = builder.Build();

  const string SECRET = "<SECRET>"; // Substitute secret

  app.MapPost("/endpoint", async (HttpRequest request) =>
  {
      using var reader = new StreamReader(request.Body);
      var body = await reader.ReadToEndAsync();

      // Get the signature from the headers
      string signature = request.Headers["X-Loop-Signature"].ToString();

      // Calculate the HMAC
      using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SECRET));
      var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
      string calculatedHmac = Convert.ToBase64String(hash);

      // Verify the signature
      if (calculatedHmac != signature)
      {
          return Results.StatusCode(401, "Invalid signature");
      }

      return Results.Json(new { signature, jsonPayload = body });
  });

  app.Run();
  ```

  ```go Go theme={null}
  package main

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/base64"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  const secret = "<SECRET>" // Substitute secret

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
  	body, err := io.ReadAll(r.Body)
  	if err != nil {
  		http.Error(w, "Failed to read request body", http.StatusInternalServerError)
  		return
  	}
  	defer r.Body.Close()

  	// Get signature from header
  	signature := r.Header.Get("X-Loop-Signature")

  	// Calculate HMAC
  	h := hmac.New(sha256.New, []byte(secret))
  	h.Write(body)
  	calculatedHMAC := base64.StdEncoding.EncodeToString(h.Sum(nil))

  	// Verify signature
  	if calculatedHMAC != signature {
  		http.Error(w, "Invalid signature", http.StatusUnauthorized)
  		return
  	}

  	// Convert payload to JSON string
  	var jsonPayload map[string]interface{}
  	if err := json.Unmarshal(body, &jsonPayload); err != nil {
  		http.Error(w, "Invalid JSON", http.StatusBadRequest)
  		return
  	}

  	w.Header().Set("Content-Type", "application/json")
  	w.WriteHeader(http.StatusOK)
  	fmt.Fprintf(w, `{"signature": "%s", "jsonPayload": %s}`, signature, string(body))
  }

  func main() {
  	http.HandleFunc("/endpoint", webhookHandler)
  	fmt.Println("Server listening on port 3000...")
  	http.ListenAndServe(":3000", nil)
  }
  ```
</CodeGroup>
