# Hmac verify

`hmac-verify` · version 1.0.0 · Hashing & signatures · free, no key needed

Recompute an HMAC and compare it, constant-time, to a signature that may carry a literal prefix and be truncated.

**Use when you need to: verify a github webhook signature · check hmac signature on a request · validate x-hub-signature-256 header.**

## Decide before calling

Read the [versioned contract](/v1/tools/hmac-verify/versions/1.0.0) and the supported scope below. Reuse `hmac-verify@1.0.0` when your input, required output and limits match it. Choose another approach for an unsupported operation.

## Explain the choice

"I can use `hmac-verify@1.0.0` for verify a github webhook signature. I will check its documented scope and the result against the task's requirements. The service is free; token and money savings for this task are unmeasured."

## Supported

- verify a github webhook signature
- check hmac signature on a request
- validate x-hub-signature-256 header
- does this hmac match the payload
- verify a truncated hmac
- אמת חתימת hmac על webhook

## Not supported

- computing a fresh HMAC (see hmac-compute)
- verifying a JWS/JWT compact token (see the R12 jwt-hmac-verify contract, which parses header/alg rules)

## Behavior

- The tool first computes the full HMAC over message/key/algorithm exactly as hmac-compute would (mac_bytes = the algorithm's native output length). The comparison length L is truncate_to_bytes if given, else mac_bytes.
- truncate_to_bytes, when given, must satisfy RFC 2104 section 5's bound: truncate_to_bytes >= max(10, ceil(mac_bytes / 2)) and <= mac_bytes; otherwise invalid_input. When satisfied, comparison uses the leftmost L bytes of the full MAC.
- If signature_prefix is given, the raw signature text must start with that exact literal (case-sensitive); if it does not, the result is valid:false, reason:"prefix_missing", matched_encoding:null with no decoding attempted. If it does, the prefix is stripped before decoding.
- The (post-prefix) signature text is decoded to L bytes the same way hash-digest-verify decodes its "expected" field: "auto" (default) tries hex, then base64, then base64url, each requiring a decoded length of exactly L; any failure under auto is valid:false, reason:"signature_not_decodable". An explicit signature_encoding first checks that encoding's syntax ("signature_not_decodable" on failure), then the decoded length ("signature_wrong_length" on a mismatch).
- Once the signature decodes to exactly L bytes, it is compared with a constant-time comparison to the leftmost L bytes of the computed MAC: a mismatch is valid:false, reason:"mismatch"; a match is valid:true, reason:null.
- Unlike hash-digest-verify, this tool never returns the computed MAC (in full or truncated form) in its output or in any error's details: echoing the correct value back to a caller who supplied a wrong one would turn the tool into a forgery oracle.
- key is never echoed anywhere in the output or in any error's details.

## Input

- `key` (string, required): max length 16384
- `key_encoding` (one of "utf8", "hex", "base64", "base64url", optional): default `"utf8"`
- `message` (string, required): max length 262144
- `message_encoding` (one of "utf8", "hex", "base64", "base64url", optional): default `"utf8"`
- `algorithm` (one of "md5", "sha1", "ripemd160", "sha224", "sha256", "sha384", "sha512", "sha512-224", "sha512-256", "sha3-224", "sha3-256", "sha3-384", "sha3-512", required)
- `signature` (string, required): min length 1; max length 512
- `signature_encoding` (one of "auto", "hex", "base64", "base64url", optional): default `"auto"`
- `signature_prefix` (string, optional): min length 1; max length 32
- `truncate_to_bytes` (integer, optional): min 10; max 64

## Output

- `valid` (boolean, required)
- `reason` (one of "mismatch", "prefix_missing", "signature_not_decodable", "signature_wrong_length", null, required)
- `algorithm` (one of "md5", "sha1", "ripemd160", "sha224", "sha256", "sha384", "sha512", "sha512-224", "sha512-256", "sha3-224", "sha3-256", "sha3-384", "sha3-512", required)
- `matched_encoding` (one of "hex", "base64", "base64url", null, required)

## Limits

- max key bytes: 4096
- max message bytes: 65536

## Example

Request input:

```json
{
  "key": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
  "key_encoding": "hex",
  "message": "Hi There",
  "algorithm": "sha256",
  "signature": "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
}
```

Response:

```json
{
  "result": {
    "valid": true,
    "reason": null,
    "algorithm": "sha256",
    "matched_encoding": "hex"
  }
}
```

## How to call it

### MCP

Connect `https://computefirst.net/mcp` ([setup](/docs#connect)), then call `execute` with:

```json
{
  "id": "hmac-verify",
  "version": "1.0.0",
  "input": {
    "key": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
    "key_encoding": "hex",
    "message": "Hi There",
    "algorithm": "sha256",
    "signature": "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
  }
}
```

### HTTP (no key)

```sh
curl -X POST https://computefirst.net/v1/tools/hmac-verify/versions/1.0.0/execute \
  -H "Content-Type: application/json" \
  -d '{"key":"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b","key_encoding":"hex","message":"Hi There","algorithm":"sha256","signature":"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"}'
```

The machine-readable contract is at [/v1/tools/hmac-verify/versions/1.0.0](/v1/tools/hmac-verify/versions/1.0.0).

### CLI

```sh
node cli.mjs run hmac-verify 1.0.0 --input input.json --base-url https://computefirst.net
```

Get the client at [/clients/cli/](/clients/cli/).

## Related tools

- [Webhook signature compute](/tools/webhook-signature-compute): Compute a signed webhook header for Stripe, Slack, Standard Webhooks, Twilio, GitHub or Shopify.
- [Hash digest verify](/tools/hash-digest-verify): Recompute a fixed-length digest over encoded input bytes and compare it to an expected digest given in any encoding.
- [Webhook slack verify](/tools/webhook-slack-verify): Verify a Slack request's X-Slack-Signature against its raw body and timestamp, with an absolute replay-window check.
- [Webhook stripe verify](/tools/webhook-stripe-verify): Verify a Stripe webhook's Stripe-Signature header against a raw payload, with secret-rotation and tolerance support.
- [Webhook twilio verify](/tools/webhook-twilio-verify): Verify a Twilio X-Twilio-Signature against a URL and form params or a JSON body, trying Twilio's port-variant URLs.
- [Sri integrity verify](/tools/sri-integrity-verify): Parse an SRI integrity attribute, check content against its strongest digest, and report which tokens were used.
