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

# Verify requests

PaySway signs each webhook request by computing an HMAC-SHA256 hash of the raw request body concatenated with a timestamp.
This hash is generated using a secret value that only you and PaySway know. The resulting signature is provided in the `X-PaySway-Signature` header.
By replicating this same process with your secret, you can confirm that the webhook request is authentic and has not been tampered with.

<Steps>
  <Step title="Obtain your secret">
    When you [create a webhook subscription](/webhooks/subscribe-to-events), the response includes a `secret` field that is base64-encoded. You must decode this string before using it to generate an HMAC signature.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const base64Secret = "zTOJGr3vYdAHM/F5ZiDsVvgPZq5/Y3Ktbo9xw9Ncf8Y=";
      const decodedSecret = Buffer.from(base64Secret, "base64");
      ```

      ```java Java theme={null}
      import java.util.Base64;
      // ...
      String base64Secret = "zTOJGr3vYdAHM/F5ZiDsVvgPZq5/Y3Ktbo9xw9Ncf8Y=";
      byte[] decodedSecret = Base64.getDecoder().decode(base64Secret);
      ```
    </CodeGroup>
  </Step>

  <Step title="Extract the timestamp and signature">
    PaySway includes an `X-PaySway-Signature` header in each webhook request. This header contains two key-value pairs separated by commas:

    * `t`: The UNIX timestamp of when the message was signed
    * `v1`: The actual signature in hexadecimal format

    **Example Header**

    ```text Signature header theme={null}
    X-PaySway-Signature: t=1738002855,v1=c9854765d242b9078e68b6fca1755f208ba70a7aa7c372abc4ec341483e34496
    ```

    Parse the header to extract the `t` and `v1` values. Ignore any other values that may appear in the header.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const signatureHeader = "t=1738002855,v1=c9854765d242b9078e68b6fca1755f208ba70a7aa7c372abc4ec341483e34496";
      const segments = signatureHeader.split(",");
      const timestamp = segments.find(s => s.startsWith("t=")).substring(2);
      const signature = segments.find(s => s.startsWith("v1=")).substring(3);
      ```

      ```java Java theme={null}
      import java.util.Arrays;
      // ...
      String signatureHeader = "t=1738002855,v1=c9854765d242b9078e68b6fca1755f208ba70a7aa7c372abc4ec341483e34496";
      String[] segments = signatureHeader.split(",");
      String timestamp = Arrays.stream(segments)
        .filter(s -> s.startsWith("t="))
        .map(s -> s.substring(2))
        .findFirst()
        .orElseThrow();
      String signature = Arrays.stream(segments)
        .filter(s -> s.startsWith("v1="))
        .map(s -> s.substring(3))
        .findFirst()
        .orElseThrow();
      ```
    </CodeGroup>
  </Step>

  <Step title="Reconstruct the signing payload">
    PaySway signs the combination of the timestamp and raw request body, separated by a period

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const rawBody = '{"foo":"bar"}'; 
      const signingPayload = `${timestamp}.${rawBody}`;
      ```

      ```java Java theme={null}
      String rawBody = "{\"foo\":\"bar\"}";
      String signingPayload = timestamp + "." + rawBody;
      ```
    </CodeGroup>

    <Warning>
      Do not parse or modify the request body before verification. Use the raw, unmodified payload exactly as received, preserving all whitespace and formatting.
    </Warning>
  </Step>

  <Step title="Generate the expected signature">
    Use your webhook secret to compute the HMAC-SHA256 hash of the signing payload. Convert the resulting hash to a hexadecimal string for comparison.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      import { createHmac } from "crypto";
      // ...
      const expectedSignature = createHmac("sha256", decodedSecret)
        .update(signingPayload, "utf8")
        .digest("hex");
      ```

      ```java Java theme={null}
      import javax.crypto.Mac;
      import javax.crypto.spec.SecretKeySpec;
      // ..
      Mac sha256 = Mac.getInstance("HmacSHA256");
      SecretKeySpec secretKey = new SecretKeySpec(decodedSecret, "HmacSHA256");
      sha256.init(secretKey);
      byte[] hash = sha256.doFinal(signingPayload.getBytes("UTF-8"));
      StringBuilder expectedSignatureBuilder = new StringBuilder();
      for (byte b : hash) {
        expectedSignatureBuilder.append(String.format("%02x", b));
      }
      String expectedSignature = expectedSignatureBuilder.toString();
      ```
    </CodeGroup>
  </Step>

  <Step title="Verify the signature and timestamp">
    Compare your `expectedSignature` with the `v1` value from the `X-PaySway-Signature` header:

    * **If they match**: The request is authentic and was signed by PaySway using the correct secret
    * **If they don't match**: Reject the request as potentially malicious or corrupted

    Additionally, use the timestamp `t` to implement replay attack protection by setting a maximum acceptable age for requests (e.g., 5 minutes).
  </Step>
</Steps>
