Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt

Use this file to discover all available pages before exploring further.

Inbound Webhook format guide

This page is the wire-level reference for each format the Inbound Webhooks verifier supports. If you’re integrating a custom provider, the rules below tell you exactly what KnoxCall computes when it verifies a request. All formats use HMAC-SHA256 with the shared secret you configured on the subscription. Comparisons are constant-time (crypto.timingSafeEqual) — there’s no timing oracle to exploit.

Stripe

Header: Stripe-Signature: t=<unix-seconds>,v1=<hex-sha256> (multiple v1=... pairs accepted — Stripe rotates secrets this way during a cutover window) Signed payload: <timestamp>.<body> — the t= value, a literal ., then the raw request body. Replay protection: yes — KnoxCall rejects with replay_window_exceeded if now - t > replay_window_seconds. Default 300 (5 min) matches Stripe’s recommendation. Compatibility: anything that adopted Stripe’s signature format. The verifier accepts a request when any of the listed v1= values matches; that’s how Stripe handles secret rotation. If you rotate the secret on KnoxCall, an in-flight cutover where the upstream sends both old and new signatures will keep working until you update the upstream config.

GitHub

Header: X-Hub-Signature-256: sha256=<hex-sha256> Signed payload: the raw body bytes only. Replay protection: no — GitHub webhooks include a delivery ID but don’t sign it, so KnoxCall can’t enforce a timestamp window. If your application can’t tolerate replay (e.g. webhooks that mutate state by content rather than idempotency key), enforce the check on your side using GitHub’s X-GitHub-Delivery header. Compatibility: GitHub webhooks, Postmark, SendGrid Inbound Parse, and most internal SaaS that copied GitHub’s scheme.

Slack

Headers:
  • X-Slack-Signature: v0=<hex-sha256>
  • X-Slack-Request-Timestamp: <unix-seconds>
Signed payload: v0:<timestamp>:<body> — the literal string v0:, the timestamp, another :, then the raw body. Replay protection: yes — KnoxCall enforces the configured window against X-Slack-Request-Timestamp. Default 300 matches Slack’s documented recommendation. Compatibility: Slack events, slash commands, interactive components.

AWS SNS (HMAC variant)

Headers (outbound):
  • x-amz-sns-signature: <base64-hmac-sha256>
  • x-amz-sns-signature-version: 1-hmac
Signed payload: the raw body only. Replay protection: no. Inbound verification: KnoxCall checks only x-amz-sns-signature. The x-amz-sns-signature-version header is present on outbound deliveries but not required for inbound verification.
⚠️ This is not real AWS SNS. Real SNS uses RSA over a canonicalised message envelope, not HMAC. KnoxCall’s aws-sns format is an HMAC variant intended for KnoxCall-to-KnoxCall flows or internal services that wanted SNS-shaped headers without the RSA tooling. If you’re verifying real AWS-issued SNS messages, this format won’t match — let us know if RSA SNS is a deal-breaker for you.

KnoxCall legacy

Header: X-Webhook-Signature: sha256=<hex-sha256> Signed payload: the raw body only. Replay protection: no. This is the format KnoxCall’s outbound Webhook Signing emitted before the multi-format library landed. Use it when both ends are KnoxCall (KnoxCall-to-KnoxCall internal events, or one KnoxCall tenant fanning to another).

Custom

Header: (caller-named, e.g. X-MyApp-Signature) with value sha256=<hex-sha256> Signed payload: the raw body only. Replay protection: no. For internal tools that don’t match a named format. Set custom_header_name when you create the subscription. The verifier only knows that one HMAC-SHA256 of the body is in the named header — it doesn’t try to be clever about timestamps.

Reproducing a verification yourself

If you want to confirm KnoxCall’s behaviour from the outside (or reproduce why a request failed), here’s the GitHub-format check in seven lines of Node:
import crypto from 'node:crypto';

const secret = 'whsec_...';
const body   = '...raw bytes from the request...';
const header = 'sha256=...';  // X-Hub-Signature-256

const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
console.log(ok);  // → true if KnoxCall would also accept this request
For Stripe, the body becomes ${timestamp}.${rawBody} and the header parses out as t=...,v1=.... The Slack version uses v0:${timestamp}:${rawBody} and the X-Slack-Signature: v0= header. The verifier source is at src/inbound-webhooks/verifier.ts if you want to read the canonical implementation.

What happens when verification fails

KnoxCall surfaces the precise reason in the signature_error column of API Logs and (when forwarding with on_invalid='forward_with_error_header') in the X-Knox-Verify-Reason HTTP header. Common values:
ReasonMeaning
missing <header> headerThe request didn’t include the header KnoxCall expected for this format
malformed <header>The header was present but didn’t match the format’s regex (e.g. not sha256=<64-hex>)
replay_window_exceededTimestamp was outside [now − replay_window_seconds, now + replay_window_seconds]
no v1 signature matched (Stripe)Header parsed correctly but none of the listed v1= values verified — most often a wrong-secret or rotated-secret-on-only-one-side scenario
Stripe-Signature missing t=The Stripe header didn’t carry a timestamp; usually a misconfigured custom shim claiming to be Stripe

Next steps