Signature Verification

Verify that webhook POSTs originated from ChatYug before processing the JSON body.

When a signature is sent

If you configured a secret key in Webhook Settings, ChatYug includes:

X-Chatyug-Signature: sha256=<hex_digest>

If no secret is configured, the signature header is omitted.

Algorithm

  1. Read the raw request body as UTF-8 text (before JSON parsing).
  2. Compute HMAC-SHA256 of that string using your webhook secret as the key.
  3. Hex-encode the digest and prefix with sha256=.
  4. Compare with the X-Chatyug-Signature header using a constant-time comparison.

Additional header

X-Chatyug-Event-Id duplicates the eventId field in the JSON body. Use it for logging and idempotency checks.

Verification examples

const crypto = require('crypto');

// Use express.raw({ type: 'application/json' }) on the webhook route
function verifyChatyugWebhook(rawBody, signatureHeader, secret) {
  if (!secret) return true;
  if (!signatureHeader) return false;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
import hmac
import hashlib

def verify_chatyug_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not secret:
        return True
    if not signature_header:
        return False
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
function verifyChatyugWebhook(string $rawBody, ?string $signature, ?string $secret): bool {
    if (!$secret) return true;
    if (!$signature) return false;
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signature);
}
Important: Verify the signature against the raw body bytes. If your framework parses JSON first, the re-serialized string may not match and verification will fail.

Configure your secret in Webhook Settings (login required). The secret is separate from your API key — see Authentication.