{"openapi":"3.0.3","info":{"title":"Webhook Signature Verifier API","version":"1.0.0","description":"Constant-time WebCrypto HMAC signature verification and replay-window check for Stripe, GitHub, Slack, Shopify, Svix, and generic HMAC webhooks."},"servers":[{"url":"https://api.tinplate.dev","description":"Production Edge Gateway"}],"components":{"schemas":{"WebhookVerifyResponse":{"type":"object","properties":{"valid":{"type":"boolean","example":true},"provider":{"type":"string","enum":["stripe","github","slack","shopify","svix","generic_hmac"],"example":"stripe","description":"Webhook provider signature scheme to verify."},"reason":{"type":"string","nullable":true,"example":null},"matched_scheme":{"type":"string","nullable":true,"example":"v1"},"timestamp_epoch":{"type":"integer","nullable":true,"example":1794628800},"timestamp_age_seconds":{"type":"integer","nullable":true,"example":12},"timestamp_valid":{"type":"boolean","nullable":true,"example":true}},"required":["valid","provider","reason","matched_scheme","timestamp_epoch","timestamp_age_seconds","timestamp_valid"]},"WebhookVerifyRequest":{"type":"object","properties":{"provider":{"type":"string","enum":["stripe","github","slack","shopify","svix","generic_hmac"],"example":"stripe","description":"Webhook provider signature scheme to verify."},"raw_body":{"type":"string","maxLength":262144,"example":"{\"id\":\"evt_123\",\"type\":\"invoice.paid\"}","description":"Exact raw HTTP request body string (must not be re-serialized)."},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":4096},"example":{"stripe-signature":"t=1794628800,v1=0000000000000000000000000000000000000000000000000000000000000000"},"description":"Incoming HTTP headers map (case-insensitive keys)."},"secret":{"type":"string","minLength":1,"maxLength":1024,"example":"whsec_test_secret","description":"Webhook signing secret (processed strictly in memory, never logged)."},"tolerance_seconds":{"type":"integer","minimum":0,"maximum":86400,"example":300,"description":"Maximum allowed timestamp age in seconds for timestamped schemes (default 300; 0 disables)."},"now_epoch_seconds":{"type":"integer","minimum":0,"description":"Optional reference Unix timestamp in seconds (defaults to current time)."},"generic_options":{"type":"object","properties":{"header_name":{"type":"string","minLength":1,"maxLength":128},"algorithm":{"type":"string","enum":["sha256","sha512"]},"encoding":{"type":"string","enum":["hex","base64"]},"prefix":{"type":"string","maxLength":64}},"description":"Options when provider is 'generic_hmac'."}},"required":["provider","raw_body","headers","secret"]},"ResponseMeta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid"},"ms":{"type":"integer","minimum":0},"version":{"type":"string","example":"1.0.0"}}},"ErrorEnvelope":{"type":"object","required":["ok","error","meta"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"invalid_input"},"message":{"type":"string","example":"Request body failed validation"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}}},"parameters":{}},"paths":{"/v1/webhook-verify":{"post":{"tags":["12_WEBHOOK_VERIFY"],"summary":"Verify an incoming webhook signature and timestamp tolerance","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookVerifyRequest"}}}},"responses":{"200":{"description":"Webhook verification result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/WebhookVerifyResponse"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}},"required":["ok","data","meta"]}}}},"400":{"description":"Invalid input or malformed JSON request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid authentication credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"Request payload exceeds subscribed plan byte cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}