Skip to main content
Materi uses GitHub-style webhook signatures in several places: a request body is signed with HMAC-SHA256, and the signature is carried in the X-Hub-Signature-256 header as:
  • sha256=<hex>
This recipe is aligned with the Shield implementation in domain/shield/apps/cicd/webhooks.py.

Inputs

You need three pieces of data:
  • payload_bytes: the raw HTTP request body bytes (before JSON parsing)
  • signature_header: the header value from X-Hub-Signature-256
  • secret: the shared secret configured on both sides

Python

This matches the Shield implementation in domain/shield/apps/cicd/webhooks.py.
import hashlib
import hmac

def verify_signature(payload_bytes: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header or not secret:
        return False

    if "=" not in signature_header:
        return False

    algo, expected = signature_header.split("=", 1)
    if algo != "sha256":
        return False

    computed = hmac.new(secret.encode("utf-8"), payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, expected)
Common pitfalls:
  • Verify against the raw request body bytes (before JSON parsing).
  • Use constant-time comparison (hmac.compare_digest).

Node.js

This matches the Shield implementation in domain/shield/apps/cicd/webhooks.py.
import crypto from 'node:crypto';

export function verifySignature(payloadBytes, signatureHeader, secret) {
    if (!signatureHeader || !secret) return false;
    const parts = signatureHeader.split('=');
    if (parts.length < 2) return false;

    const algo = parts[0];
    const expected = parts.slice(1).join('=');
    if (algo !== 'sha256') return false;

    const computed = crypto
        .createHmac('sha256', Buffer.from(secret, 'utf8'))
        .update(payloadBytes)
        .digest('hex');

    return crypto.timingSafeEqual(Buffer.from(computed, 'utf8'), Buffer.from(expected, 'utf8'));
}
Notes:
  • Pass the raw bytes (e.g., Buffer), not a parsed JSON object.
  • timingSafeEqual requires buffers of equal length; using the computed/expected hex strings avoids length mismatches unless the header is malformed.

Receiver checklist

  • Reject missing/invalid signatures with 401.
  • Verify against raw bytes.
  • Use constant-time comparison.
  • Log only high-level info (avoid logging secrets or full payloads in production).