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

# Webhook Verification

> xPay supports two methods for webhook signature verification. Both are valid and supported. Please use the one that matches your integration.

## 1. HMAC-SHA512

This is the default method. Each event payload is signed using HMAC-SHA512 with your provided `signing_key`. The signature is sent in the `xpay-signature` header.

<CodeGroup>
  ```java Java theme={null}
  private boolean verifySignature(String incomingSignature, String payload, String secret) throws Exception {
          Mac sha512Hmac = Mac.getInstance("HmacSHA512");
          SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA512");
          sha512Hmac.init(keySpec);
          byte[] macData = sha512Hmac.doFinal(payload.getBytes("UTF-8"));
          incomingSignature.equals(Base64.getEncoder().encodeToString(macData));
      }
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifySignature(incomingSignature, payload, secret) {
      const hmac = crypto.createHmac('sha512', secret);
      hmac.update(payload);
      const generatedSignature = hmac.digest('base64');
      
      return incomingSignature === generatedSignature;
  }

  // Usage example
  const incomingSignature = 'some-signature';
  const payload = 'payload-data';
  const secret = 'secret-key';
  const isValid = verifySignature(incomingSignature, payload, secret);
  console.log(isValid);
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import base64

  def verify_signature(incoming_signature, payload, secret):
      # Create the HMAC-SHA512 signature
      mac = hmac.new(secret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha512)
      generated_signature = base64.b64encode(mac.digest()).decode('utf-8')
      
      return incoming_signature == generated_signature

  # Usage example
  incoming_signature = 'some-signature'
  payload = 'payload-data'
  secret = 'secret-key'
  is_valid = verify_signature(incoming_signature, payload, secret)
  print(is_valid)
  ```

  ```rust Rust theme={null}
  // Add Dependencies in Cargo.toml
  // [dependencies]
  // hmac = "0.11"
  // sha2 = "0.10"
  // base64 = "0.13"

  use hmac::{Hmac, Mac};
  use sha2::Sha512;
  use base64::{encode};

  // Type alias for HMAC-SHA512
  type HmacSha512 = Hmac<Sha512>;

  fn verify_signature(incoming_signature: &str, payload: &str, secret: &str) -> bool {
      let mut mac = HmacSha512::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size");
      mac.update(payload.as_bytes());
      let result = mac.finalize();
      let generated_signature = encode(result.into_bytes());

      incoming_signature == generated_signature
  }

  // Usage example
  fn main() {
      let incoming_signature = "some-signature";
      let payload = "payload-data";
      let secret = "secret-key";
      let is_valid = verify_signature(incoming_signature, payload, secret);
      println!("{}", is_valid);
  }
  ```
</CodeGroup>

## 2. ECDSA

xPay supports ECDSA signature verification for webhooks. The signature is sent in the `xpay-private-signature` header, and the public key is provided below.

#### xPay Webhook Public Key

This is the public key used for webhook signature verification. The key below is **base64 encoded**. You can use this key to verify ECDSA signatures from xPay webhooks.

```text theme={null}
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==
```

<CodeGroup>
  ```java Java theme={null}
  import java.security.*;
  import java.security.spec.*;
  import java.util.Base64;

  public class WebhookVerifier {
      public static boolean verifySignature(String publicKeyBase64, String signatureBase64, String payload) throws Exception {
          byte[] publicBytes = Base64.getDecoder().decode(publicKeyBase64);
          KeyFactory keyFactory = KeyFactory.getInstance("EC");
          X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
          PublicKey pubKey = keyFactory.generatePublic(keySpec);

          Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA");
          ecdsaVerify.initVerify(pubKey);
          ecdsaVerify.update(payload.getBytes("UTF-8"));

          byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
          return ecdsaVerify.verify(signatureBytes);
      }

      public static void main(String[] args) throws Exception {
          String publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==";
          String signature = "MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=";
          String payload = "{\"eventId\":\"whe_g00AGgDy9KeiTPj6\",...}";

          boolean isValid = verifySignature(publicKey, signature, payload);
          System.out.println("Signature valid: " + isValid);
      }
  }

  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifySignature(publicKeyBase64, signatureBase64, payload) {
      const publicKeyPem = `-----BEGIN PUBLIC KEY-----\n${publicKeyBase64.match(/.{1,64}/g).join('\n')}\n-----END PUBLIC KEY-----`;

      const verifier = crypto.createVerify('SHA256');
      verifier.update(payload);
      verifier.end();

      const signature = Buffer.from(signatureBase64, 'base64');
      return verifier.verify(publicKeyPem, signature);
  }

  // Example usage
  const isValid = verifySignature(
      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==",
      "MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=",
      '{"eventId":"whe_g00AGgDy9KeiTPj6",...}'
  );

  console.log('Signature valid:', isValid);

  ```

  ```python Python theme={null}
  import base64
  from cryptography.hazmat.primitives import hashes
  from cryptography.hazmat.primitives.asymmetric import ec
  from cryptography.hazmat.primitives.serialization import load_der_public_key

  def verify_signature(public_key_base64, signature_base64, payload):
      public_key_bytes = base64.b64decode(public_key_base64)
      public_key = load_der_public_key(public_key_bytes)
      signature = base64.b64decode(signature_base64)

      try:
          public_key.verify(signature, payload.encode('utf-8'), ec.ECDSA(hashes.SHA256()))
          return True
      except Exception:
          return False

  # Example usage
  is_valid = verify_signature(
      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==",
      "MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=",
      '{"eventId":"whe_g00AGgDy9KeiTPj6",...}'
  )

  print("Signature valid:", is_valid)

  ```

  ```rust Rust theme={null}
  use base64;
  use ring::{signature, signature::UnparsedPublicKey};

  fn verify_signature(public_key_b64: &str, signature_b64: &str, payload: &str) -> bool {
      let public_key_bytes = match base64::decode(public_key_b64) {
          Ok(bytes) => bytes,
          Err(_) => return false,
      };
      let signature_bytes = match base64::decode(signature_b64) {
          Ok(bytes) => bytes,
          Err(_) => return false,
      };

      let public_key = UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_ASN1, &public_key_bytes);
      public_key.verify(payload.as_bytes(), &signature_bytes).is_ok()
  }

  fn main() {
      let valid = verify_signature(
          "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==",
          "MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=",
          r#"{"eventId":"whe_g00AGgDy9KeiTPj6",...}"#
      );
      println!("Signature valid: {}", valid);
  }
  ```
</CodeGroup>

***

### Sample Payload Header

```json theme={null}
{
  "xpay-signature" : "s3z2v3ZGwMvDwiQ+9PpAT3WuSYU+PeSZJqvdzZXPmifAh3sT+s502PgJJx9NV4KQaScEaquTEuHJ30v17hK5GA==",
  "xpay-private-signature" : "MEYCIQD+649oWWsMkVbTcOsfkxKvxsnbAPbeq0QHptC2i1dFuwIhALN5GHEliiv+MT3/5a2ye8Wbud5rFNDRIMz6ExgWAaYg",
  ...
}
```

***
