Verify webhook signatures from 8 providers
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
| Field | Type | Required | Description |
|---|---|---|---|
source | string; one of "stripe", "github", "shopify", "slack", "twilio", "linear", "clerk", "sendgrid-hmac" | yes | |
secret | string; length 0..500 | yes | The signing secret; for Clerk/Svix a 'whsec_<base64>' value is accepted. |
rawBody | string; length 0..2000000 | yes | The exact request body bytes as a string. Reparsed JSON will not match; send the raw bytes. |
headers | object | yes | Request headers as sent by the provider (string values). Names are case-insensitive. |
url | string; length 0..2000 | no | Full request URL. Required for Twilio, ignored elsewhere. |
formParams | object | no | Form parameters parsed from a Twilio POST (string values); order does not matter. |
toleranceSeconds | integer | no | Max allowed |now - signed timestamp|. Default 300s. |
nowIso | string; length 0..40 | no | Override '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.
| Field | Type | Required | Description |
|---|---|---|---|
status | string; one of "pass", "fail", "unknown", "unsupported" | no | pass: 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. |
source | string | no | |
valid | boolean | no | |
bodyBytes | integer | no | |
witnesses | array<object> | no | |
versions | object | no | Every semantic dependency of this answer (engine, tables and their as-of dates, tokenizer, calendar, rounding profile). |
limits | array<string> | no | |
deterministic | boolean | no | Always 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
- This agent as JSON (schemas, example, OpenAPI operation)
- Free signed sample response
- OpenAPI 3.1 · Catalog · Index of every agent