Verify webhook signatures from 8 providers

$0.005 USDC per call · Developer and security · POST /api/v1/webhook-verify · Id webhook-verify

Constant-time verification of inbound webhook signatures from Stripe, GitHub, Shopify, Slack, Twilio, Linear, Clerk (Svix) and SendGrid HMAC, each using the exact signing string that provider documents. Every scheme uses Web Crypto HMAC with a timing-safe compare and, where the provider signs a timestamp, a configurable clock-skew tolerance (default 300s) to block replay. Returns the verdict and the specific failure reason: missing header, skewed timestamp, decoded-payload mismatch, or malformed secret. No network calls; the raw body and headers are the inputs.

What you get

One wrong character of normalization (newline, case, missing timestamp in the string) silently accepts forged webhooks. This is the tested reference for all eight.

Call it

Send the JSON body below to https://agents.writerslogic.com/api/v1/webhook-verify. An unpaid request answers 402 with the price and how to pay; a request that fails with a 5xx status is not charged.

curl -i -X POST https://agents.writerslogic.com/api/v1/webhook-verify \
  -H 'content-type: application/json' \
  -d '{"source":"github","secret":"mysecret","rawBody":"{\"a\":1}","headers":{"x-hub-signature-256":"sha256=0000000000000000000000000000000000000000000000000000000000000000"}}'

Input

FieldTypeRequiredDescription
sourcestring; one of "stripe", "github", "shopify", "slack", "twilio", "linear", "clerk", "sendgrid-hmac"yes
secretstring; length 0..500yesThe signing secret; for Clerk/Svix a 'whsec_<base64>' value is accepted.
rawBodystring; length 0..2000000yesThe exact request body bytes as a string. Reparsed JSON will not match; send the raw bytes.
headersobjectyesRequest headers as sent by the provider (string values). Names are case-insensitive.
urlstring; length 0..2000noFull request URL. Required for Twilio, ignored elsewhere.
formParamsobjectnoForm parameters parsed from a Twilio POST (string values); order does not matter.
toleranceSecondsintegernoMax allowed |now - signed timestamp|. Default 300s.
nowIsostring; length 0..40noOverride 'now' for deterministic tests; ISO-8601 instant.

Example request body

{
  "source": "github",
  "secret": "mysecret",
  "rawBody": "{\"a\":1}",
  "headers": {
    "x-hub-signature-256": "sha256=0000000000000000000000000000000000000000000000000000000000000000"
  }
}

Result

The response is { "data": ..., "_provenance": ... }. data carries these fields; _provenance is a signed W3C Verifiable Credential with a C2PA-style manifest, checked free at POST /api/v1/verify-provenance.

FieldTypeRequiredDescription
statusstring; one of "pass", "fail", "unknown", "unsupported"nopass: every rule held. fail: a rule was violated (see witnesses). unknown: the input lacks the evidence to decide. unsupported: outside what this engine covers. Missing evidence is never a pass.
sourcestringno
validbooleanno
bodyBytesintegerno
witnessesarray<object>no
versionsobjectnoEvery semantic dependency of this answer (engine, tables and their as-of dates, tokenizer, calendar, rounding profile).
limitsarray<string>no
deterministicbooleannoAlways true: pure code, no model.

Pay and connect

Available through: x402 (USDC on Base), MCP. MCP clients reach it through the code-preflight-kit package.

Machine-readable