[{"data":1,"prerenderedAt":205},["ShallowReactive",2],{"doc-article-webhooks-signature-validation":3,"doc-siblings-webhooks":200},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"category":5,"order":10,"difficulty":11,"time_to_complete":12,"related":13,"code_curl":15,"code_node":16,"code_python":17,"code_go":18,"body":19,"_type":194,"_id":195,"_source":196,"_file":197,"_stem":198,"_extension":199},"\u002Fdeveloper\u002Fwebhooks\u002Fsignature-validation","webhooks",false,"","Signature Validation","Verify outbound webhook payloads authenticity using standard HMAC-SHA256 headers validation.",2,"advanced","6 minutes",[14],"\u002Fdocs\u002Fwebhooks\u002Fwebhook-events","# Send a manual test hook mimicking secure header validation\ncurl -X POST \"http:\u002F\u002Flocalhost:8080\u002Fwebhook\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -H \"X-AISoule-Signature: sha256=17df2033bc653842c019d6575791a82f3c0b5f1025de03194a0d9b4b0a48a9b2\" \\\n  -d '{\"event\":\"ping\"}'\n","\u002F\u002F Verify X-AISoule-Signature using Node.js crypto module\nconst crypto = require('crypto');\nconst express = require('express');\nconst app = express();\n\n\u002F\u002F Parse raw body string for accurate cryptographic checking\napp.use(express.json({\n  verify: (req, res, buf) => { req.rawBody = buf; }\n}));\n\napp.post('\u002Fwebhook', (req, res) => {\n  const signature = req.headers['x-aisoule-signature'];\n  if (!signature) return res.sendStatus(401);\n  \n  const computedSig = crypto\n    .createHmac('sha256', 'YOUR_WEBHOOK_SECRET')\n    .update(req.rawBody)\n    .digest('hex');\n    \n  if (`sha256=${computedSig}` !== signature) {\n    return res.sendStatus(403); \u002F\u002F Signature mismatch\n  }\n  \n  console.log('Webhook validated successfully! ✅');\n  res.sendStatus(200);\n});\n\napp.listen(8080);\n","# Verify signature using Python hashlib & hmac\nimport hmac\nimport hashlib\nfrom flask import Flask, request, abort\n\napp = Flask(__name__)\nSECRET = b\"YOUR_WEBHOOK_SECRET\"\n\n@app.route(\"\u002Fwebhook\", methods=[\"POST\"])\ndef handle_webhook():\n    signature = request.headers.get(\"X-AISoule-Signature\")\n    if not signature:\n        abort(401)\n        \n    # Calculate local hash using raw bytes data\n    computed = hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()\n    \n    if f\"sha256={computed}\" != signature:\n        abort(403) # Invalid request\n        \n    print(\"Validated successfully! ✅\")\n    return \"\", 200\n\nif __name__ == \"__main__\":\n    app.run(port=8080)\n","\u002F\u002F Verify signature in Go\npackage main\n\nimport (\n    \"crypto\u002Fhmac\"\n    \"crypto\u002Fsha256\"\n    \"encoding\u002Fhex\"\n    \"io\"\n    \"net\u002Fhttp\"\n)\n\nconst Secret = \"YOUR_WEBHOOK_SECRET\"\n\nfunc handleWebhook(w http.ResponseWriter, r *http.Request) {\n    sig := r.Header.Get(\"X-AISoule-Signature\")\n    bodyBytes, _ := io.ReadAll(r.Body)\n    \n    h := hmac.New(sha256.New, []byte(Secret))\n    h.Write(bodyBytes)\n    expectedSig := \"sha256=\" + hex.EncodeToString(h.Sum(nil))\n    \n    if sig != expectedSig {\n        w.WriteHeader(http.StatusForbidden)\n        return\n    }\n    \n    w.WriteHeader(http.StatusOK)\n}\n\nfunc main() {\n    http.HandleFunc(\"\u002Fwebhook\", handleWebhook)\n    http.ListenAndServe(\":8080\", nil)\n}\n",{"type":20,"children":21,"toc":190},"root",[22,31,37,44,49,62,90,94,100,105,168],{"type":23,"tag":24,"props":25,"children":27},"element","h1",{"id":26},"secure-webhook-signature-validation",[28],{"type":29,"value":30},"text","Secure Webhook Signature Validation",{"type":23,"tag":32,"props":33,"children":34},"p",{},[35],{"type":29,"value":36},"To protect your receiving server from Man-in-the-Middle (MitM) attacks or spoofing, AISoule signs all outbound webhook POST requests. You can cryptographic check this signature to verify that the query was dispatched exclusively by AISoule.",{"type":23,"tag":38,"props":39,"children":41},"h2",{"id":40},"the-validation-header",[42],{"type":29,"value":43},"The Validation Header",{"type":23,"tag":32,"props":45,"children":46},{},[47],{"type":29,"value":48},"Every webhook request includes a security signature header:",{"type":23,"tag":50,"props":51,"children":56},"pre",{"className":52,"code":54,"language":55,"meta":7},[53],"language-http","X-AISoule-Signature: sha256=17df2033bc653842c019d6575791a82f3c0b5f1025de03194a0d9b4b0a48a9b2\n","http",[57],{"type":23,"tag":58,"props":59,"children":60},"code",{"__ignoreMap":7},[61],{"type":29,"value":54},{"type":23,"tag":32,"props":63,"children":64},{},[65,67,73,75,81,83,88],{"type":29,"value":66},"This header contains the prefix ",{"type":23,"tag":58,"props":68,"children":70},{"className":69},[],[71],{"type":29,"value":72},"sha256=",{"type":29,"value":74}," followed by a hex-encoded ",{"type":23,"tag":76,"props":77,"children":78},"strong",{},[79],{"type":29,"value":80},"HMAC-SHA256",{"type":29,"value":82}," digest of the raw JSON request body payload, generated using your webhook configuration's ",{"type":23,"tag":76,"props":84,"children":85},{},[86],{"type":29,"value":87},"Secret Key",{"type":29,"value":89}," as the cryptographic salt.",{"type":23,"tag":91,"props":92,"children":93},"hr",{},[],{"type":23,"tag":38,"props":95,"children":97},{"id":96},"step-by-step-validation-guide",[98],{"type":29,"value":99},"Step-by-Step Validation Guide",{"type":23,"tag":32,"props":101,"children":102},{},[103],{"type":29,"value":104},"To validate incoming payloads:",{"type":23,"tag":106,"props":107,"children":108},"ol",{},[109,120,143],{"type":23,"tag":110,"props":111,"children":112},"li",{},[113,118],{"type":23,"tag":76,"props":114,"children":115},{},[116],{"type":29,"value":117},"Extract the raw body",{"type":29,"value":119},": Retrieve the raw request body bytes before your framework parses it into JSON.",{"type":23,"tag":110,"props":121,"children":122},{},[123,128,130,135,137,141],{"type":23,"tag":76,"props":124,"children":125},{},[126],{"type":29,"value":127},"Compute local HMAC",{"type":29,"value":129},": Generate an ",{"type":23,"tag":58,"props":131,"children":133},{"className":132},[],[134],{"type":29,"value":80},{"type":29,"value":136}," hash of the raw body bytes, using your webhook's unique ",{"type":23,"tag":76,"props":138,"children":139},{},[140],{"type":29,"value":87},{"type":29,"value":142}," (from Settings dashboard).",{"type":23,"tag":110,"props":144,"children":145},{},[146,151,153,158,160,166],{"type":23,"tag":76,"props":147,"children":148},{},[149],{"type":29,"value":150},"Compare hashes securely",{"type":29,"value":152},": Use a ",{"type":23,"tag":76,"props":154,"children":155},{},[156],{"type":29,"value":157},"constant-time string comparison",{"type":29,"value":159}," function to prevent timing attacks. Verify if your computed hash matches the ",{"type":23,"tag":58,"props":161,"children":163},{"className":162},[],[164],{"type":29,"value":165},"X-AISoule-Signature",{"type":29,"value":167}," header value.",{"type":23,"tag":169,"props":170,"children":171},"blockquote",{},[172],{"type":23,"tag":32,"props":173,"children":174},{},[175,181,183,188],{"type":23,"tag":176,"props":177,"children":178},"span",{},[179],{"type":29,"value":180},"!WARNING",{"type":29,"value":182},"\nDo NOT parse the request body to JSON first, as formatting changes (spaces, line breaks) will cause signature validation failure. Always compute the hash using the ",{"type":23,"tag":76,"props":184,"children":185},{},[186],{"type":29,"value":187},"raw, original payload bytes",{"type":29,"value":189},".",{"title":7,"searchDepth":10,"depth":10,"links":191},[192,193],{"id":40,"depth":10,"text":43},{"id":96,"depth":10,"text":99},"markdown","content:developer:webhooks:signature-validation.md","content","developer\u002Fwebhooks\u002Fsignature-validation.md","developer\u002Fwebhooks\u002Fsignature-validation","md",[201,202],{"_path":4,"title":8},{"_path":203,"title":204},"\u002Fdeveloper\u002Fwebhooks\u002Fwebhook-events","Webhook Events",1780423179018]