{"id":"webhook-signature-compute","version":"1.0.0","description":"Compute a signed webhook header for Stripe, Slack, Standard Webhooks, Twilio, GitHub or Shopify.","supported_operations":["sign a test webhook payload like stripe would","generate a fake but valid slack signature for testing","compute x-hub-signature-256 for github","build a twilio x-twilio-signature for a test request","shopify webhook hmac header for testing","standard webhooks signature header"],"unsupported_operations":["verifying a received signature against a claimed secret (see the four webhook-*-verify tools)","verifying a received GitHub or Shopify signature (no standalone verify tool for either; see the family README's rejected-tools list and use hmac-verify presets to check them)"],"semantics":["scheme 'stripe': signature = lowercase-hex(HMAC-SHA256(secret as UTF-8 verbatim, '{timestamp}.{payload}')); headers = [{name:'Stripe-Signature', value:'t={timestamp},v1={signature}'}].","scheme 'slack': signature = 'v0=' + lowercase-hex(HMAC-SHA256(signing_secret, 'v0:{timestamp}:{body}')); headers = [{name:'X-Slack-Signature', value: signature}, {name:'X-Slack-Request-Timestamp', value: String(timestamp)}].","scheme 'standard': secret is decoded exactly as webhook-standard-verify decodes it (strip 'whsec_' if present, base64-decode tolerating missing/already-correct padding); signature = 'v1,' + base64(HMAC-SHA256(decoded secret, '{webhook_id}.{webhook_timestamp}.{payload}')); headers = [{name:'webhook-id', ...}, {name:'webhook-timestamp', ...}, {name:'webhook-signature', value: signature}].","scheme 'twilio': signature = base64(HMAC-SHA1(auth_token, url followed by, for each params key sorted ascending by Unicode code point (as Python's sorted() in twilio-python, not JS UTF-16 code-unit order), its deduplicated values sorted the same way, concatenated as key+value)) -- built directly over url as given (no port-variant guessing, since the caller controls the exact URL to sign); headers = [{name:'X-Twilio-Signature', value: signature}].","scheme 'github': signature = 'sha256=' + lowercase-hex(HMAC-SHA256(secret, payload)); headers = [{name:'X-Hub-Signature-256', value: signature}] (a fixed HMAC-SHA256-hex-with-prefix preset; there is no standalone webhook-github-verify tool).","scheme 'shopify': signature = base64(HMAC-SHA256(secret, payload)); headers = [{name:'X-Shopify-Hmac-Sha256', value: signature}] (a fixed preset, same reasoning as github).","A field belonging to a different scheme than the one chosen is invalid_input, and a field the chosen scheme requires but omits is invalid_input.","secret/signing_secret/auth_token are never echoed anywhere in the output.","scheme 'twilio': Limits on params (checked before any sorting or hashing, limit_exceeded): at most 1000 values in total (max_params_values; a string value counts 1, an array counts its length, duplicates included, before deduplication), and at most 65536 UTF-8 bytes summed over every key (once per key) and every value (each array element, duplicates included) (max_params_bytes). A real Twilio form post is a few KB with a few dozen fields."],"limits":{"max_payload_bytes":262144,"max_url_bytes":4096,"max_params_bytes":65536,"max_params_values":1000},"pricing":{"status":"unpriced","charge_usd":null},"input_schema":{"type":"object","additionalProperties":false,"required":["scheme"],"properties":{"scheme":{"type":"string","enum":["stripe","slack","standard","twilio","github","shopify"]},"payload":{"type":"string","maxLength":262144},"secret":{"type":"string","minLength":1,"maxLength":512},"timestamp":{"type":"integer","minimum":0,"maximum":99999999999},"body":{"type":"string","maxLength":262144},"signing_secret":{"type":"string","minLength":1,"maxLength":512},"webhook_id":{"type":"string","minLength":1,"maxLength":256},"webhook_timestamp":{"type":"string","pattern":"^[0-9]{1,16}$"},"url":{"type":"string","minLength":1,"maxLength":4096},"params":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1}]}},"auth_token":{"type":"string","minLength":1,"maxLength":512}},"oneOf":[{"additionalProperties":false,"required":["scheme","payload","secret","timestamp"],"properties":{"scheme":{"const":"stripe"},"payload":{"type":"string"},"secret":{"type":"string"},"timestamp":{"type":"integer"}}},{"additionalProperties":false,"required":["scheme","body","signing_secret","timestamp"],"properties":{"scheme":{"const":"slack"},"body":{"type":"string"},"signing_secret":{"type":"string"},"timestamp":{"type":"integer"}}},{"additionalProperties":false,"required":["scheme","payload","webhook_id","webhook_timestamp","secret"],"properties":{"scheme":{"const":"standard"},"payload":{"type":"string"},"webhook_id":{"type":"string"},"webhook_timestamp":{"type":"string"},"secret":{"type":"string"}}},{"additionalProperties":false,"required":["scheme","url","auth_token"],"properties":{"scheme":{"const":"twilio"},"url":{"type":"string"},"auth_token":{"type":"string"},"params":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1}]}}}},{"additionalProperties":false,"required":["scheme","payload","secret"],"properties":{"scheme":{"const":"github"},"payload":{"type":"string"},"secret":{"type":"string"}}},{"additionalProperties":false,"required":["scheme","payload","secret"],"properties":{"scheme":{"const":"shopify"},"payload":{"type":"string"},"secret":{"type":"string"}}}]},"output_schema":{"type":"object","additionalProperties":false,"required":["scheme","headers","signature"],"properties":{"scheme":{"type":"string","enum":["stripe","slack","standard","twilio","github","shopify"]},"headers":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","additionalProperties":false,"required":["name","value"],"properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"signature":{"type":"string","minLength":1}}},"examples":[{"input":{"scheme":"github","payload":"{\"zen\":\"Non-blocking is better than blocking.\"}","secret":"test-secret"},"output":{"scheme":"github","headers":[{"name":"X-Hub-Signature-256","value":"sha256=6227df376409e5e6097374f5a7e8f28f40faf1aa9b7c83df6d70b59eb087f476"}],"signature":"sha256=6227df376409e5e6097374f5a7e8f28f40faf1aa9b7c83df6d70b59eb087f476"}},{"input":{"scheme":"shopify","payload":"{\"id\":123,\"test\":true}","secret":"hush"},"output":{"scheme":"shopify","headers":[{"name":"X-Shopify-Hmac-Sha256","value":"QLIhAcxHLpXohhOp+L1RROYR5dXplO8Sa9WCdISp47E="}],"signature":"QLIhAcxHLpXohhOp+L1RROYR5dXplO8Sa9WCdISp47E="}}],"execute_url":"/v1/tools/webhook-signature-compute/versions/1.0.0/execute"}