# Webhook Signature Validator

Verify webhook signatures to ensure the authenticity and integrity of webhook payloads sent by Teya. Use the interactive validator below to test your signature verification.

## Interactive Validator

Test your webhook signature validation directly in the browser. Enter your public key, the raw payload, and the signature to verify.

<div id="signature-validator" style="background: #f9fafb; border-radius: 12px; padding: 24px; margin: 24px 0; border: 1px solid #e5e7eb;">
<div style="margin-bottom: 20px;">
<label for="validator-public-key" style="display: block; font-weight: 600; margin-bottom: 8px; color: #374151;">Public Key (Base64 DER format)</label>
<textarea id="validator-public-key" placeholder="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..." style="width: 100%; height: 120px; padding: 12px; border: 1px solid #d1d5db; border-radius: 8px; font-family: monospace; font-size: 13px; resize: vertical; background: #fff;"></textarea>
</div>
<div style="margin-bottom: 20px;">
<label for="validator-payload" style="display: block; font-weight: 600; margin-bottom: 8px; color: #374151;">Webhook Payload (raw JSON body)</label>
<textarea id="validator-payload" placeholder='{"event": "payment.succeeded.v1", "timestamp": "2025-12-02T12:04:22Z", "data": { ... }}' style="width: 100%; height: 150px; padding: 12px; border: 1px solid #d1d5db; border-radius: 8px; font-family: monospace; font-size: 13px; resize: vertical; background: #fff;"></textarea>
</div>
<div style="margin-bottom: 20px;">
<label for="validator-signature" style="display: block; font-weight: 600; margin-bottom: 8px; color: #374151;">Signature (from x-teya-signature header)</label>
<textarea id="validator-signature" placeholder="gOzDXhS7M8afuYjYx9EGTjOUXra..." style="width: 100%; height: 80px; padding: 12px; border: 1px solid #d1d5db; border-radius: 8px; font-family: monospace; font-size: 13px; resize: vertical; background: #fff;"></textarea>
</div>
<button type="button" id="validate-btn" style="background: linear-gradient(135deg, #E0E722, #c4d600); border: none; border-radius: 8px; padding: 12px 24px; font-weight: 600; font-size: 15px; cursor: pointer; color: #000; box-shadow: 0 2px 4px rgba(224, 231, 34, 0.3);"><i class="fas fa-shield-alt" style="margin-right: 8px;"></i>Validate Signature</button>
<div id="validation-result" style="margin-top: 20px; display: none;"></div>
</div>
<script>
(function initValidator() {
  var btn = document.getElementById('validate-btn');
  if (!btn) { setTimeout(initValidator, 100); return; }
  btn.addEventListener('click', async function() {
  var publicKeyPem = document.getElementById('validator-public-key').value.trim();
  var payload = document.getElementById('validator-payload').value;
  var signature = document.getElementById('validator-signature').value.trim();
  var resultDiv = document.getElementById('validation-result');
  resultDiv.style.display = 'block';
  resultDiv.innerHTML = '<div style="padding: 16px; background: #f3f4f6; border-radius: 8px; color: #6b7280;"><i class="fas fa-spinner fa-spin" style="margin-right: 8px;"></i>Validating...</div>';
  if (!publicKeyPem || !payload || !signature) {
    resultDiv.innerHTML = '<div style="padding: 16px; background: #fef3c7; border: 1px solid #f59e0b; border-radius: 8px; color: #92400e;"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i><strong>Missing Input:</strong> Please fill in all fields.</div>';
    return;
  }
  try {
    var pemHeader = '-----BEGIN PUBLIC KEY-----';
    var pemFooter = '-----END PUBLIC KEY-----';
    var pemContents = publicKeyPem;
    if (pemContents.includes(pemHeader)) {
      pemContents = pemContents.substring(pemContents.indexOf(pemHeader) + pemHeader.length);
    }
    if (pemContents.includes(pemFooter)) {
      pemContents = pemContents.substring(0, pemContents.indexOf(pemFooter));
    }
    pemContents = pemContents.replace(/\s/g, '');
    var binaryDer = Uint8Array.from(atob(pemContents), function(c) { return c.charCodeAt(0); });
    var publicKey = await crypto.subtle.importKey('spki', binaryDer, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
    var signatureBytes = Uint8Array.from(atob(signature), function(c) { return c.charCodeAt(0); });
    var encoder = new TextEncoder();
    var payloadBytes = encoder.encode(payload);
    var isValid = await crypto.subtle.verify('RSASSA-PKCS1-v1_5', publicKey, signatureBytes, payloadBytes);
    if (isValid) {
      resultDiv.innerHTML = '<div style="padding: 16px; background: #d1fae5; border: 1px solid #10b981; border-radius: 8px; color: #065f46;"><i class="fas fa-check-circle" style="margin-right: 8px;"></i><strong>Valid Signature</strong> - The webhook payload was signed with the matching private key.</div>';
    } else {
      resultDiv.innerHTML = '<div style="padding: 16px; background: #fee2e2; border: 1px solid #ef4444; border-radius: 8px; color: #991b1b;"><i class="fas fa-times-circle" style="margin-right: 8px;"></i><strong>Invalid Signature</strong> - The signature does not match the payload and public key.</div>';
    }
  } catch (error) {
    var errorDiv = document.createElement('div');
    errorDiv.textContent = error.message;
    resultDiv.innerHTML = '<div style="padding: 16px; background: #fee2e2; border: 1px solid #ef4444; border-radius: 8px; color: #991b1b;"><i class="fas fa-exclamation-circle" style="margin-right: 8px;"></i><strong>Error:</strong> ' + errorDiv.innerHTML + '</div>';
  }
});
})();
</script>
