# Parsing webhooks To parse webhooks, here is the algorithm you should/must follow: **Examples are provided in JS as of right now** - Check the protocol version: - The current protocol version is `splashtail` - Check the `X-Webhook-Protocol` header and ensure that it is equal to the current protocol version ```js if (req.headers["x-webhook-protocol"] != supportedProtocol) { reply.status(403).send({ message: "Invalid protocol version!", error: true, }); return; } ``` - A nonce is used to randomize the signature for retries. Ensure a nonce exists by checking the header's existence: ```js if (!req.headers["x-webhook-nonce"]) { reply.status(403).send({ message: "No nonce provided?", error: true, }); return; } ``` - Next calculate the expected signature - To do so, you must first get the body of the request - Then use HMAC-SHA512 with the webhook secret as key and the body as the request body to get the ``signedBody``. Note that the format/digest should be ``hex`` - Then use HMAC-SHA512 with the nonce as the key and the signed body as the message to get the expected signature. Note that the format/digest should be ``hex`` ```js let body: string = req.body; if (!body) { reply.status(400).send({ message: "No request body provided?", error: true, }); return; } // Create hmac 512 hash let signedBody = crypto .createHmac("sha512", webhookSecret) .update(body) .digest("hex"); // Create the actual signature using x-webhook-nonce by performing a second hmac let nonce = req.headers["x-webhook-nonce"].toString(); let expectedTok = crypto .createHmac("sha512", nonce) .update(signedBody) .digest("hex"); ``` - Compare this value with the ``X-Webhook-Signature`` header - If they are equal, the request is valid and you can continue processing it - If they are not equal, the request is invalid and you should return a 403 status code ```js if (req.headers["x-webhook-signature"] != expectedTok) { console.log( `Expected: ${expectedTok} Got: ${req.headers["x-webhook-signature"]}` ); reply.status(403).send({ message: "Invalid signature", }); return; } ``` - Next decrypt the request body. This is an additional security to prevent sensitive information from being leaked - First hash the concatenation of the webhook secret and the nonce using SHA256 - Then read the body as a hex string and decrypt it using AES-256-GCM with the hashed secret as the key ```js // sha256 on key let hashedKey = crypto .createHash("sha256") .update(webhookSecret + nonce) .digest(); let enc = Buffer.from(body, "hex"); const tag = enc.subarray(enc.length - tagLength, enc.length); const iv = enc.subarray(0, ivLength); const toDecrypt = enc.subarray(ivLength, enc.length - tag.length); const decipher = crypto.createDecipheriv("aes-256-gcm", hashedKey, iv); decipher.setAuthTag(tag); const res = Buffer.concat([decipher.update(toDecrypt), decipher.final()]); // Parse the decrypted body let data = JSON.parse(res.toString("utf-8")); if (data.created_at == undefined) { reply.status(400).send({ message: "Invalid body", error: true, }); return; ``` ## HTML Sanitizer We use a custom server to parse markdown (using ``pulldown-cmark``) and sanitize HTML (using ``ammonia``). This is to prevent XSS attacks and other malicious code from being injected into our site. The URL for HTML Sanitizer is ``https://hs.infinitybots.gg``. To use this for previews etc, simply POST the raw MD/HTML to this URL and it will return the sanitized HTML.