Forge signs every outbound webhook delivery with HMAC-SHA256. Verifying the signature on your server confirms the request came from Forge and that the body was not tampered with in transit.
How the signature works
Forge takes the raw JSON body bytes and computes HMAC-SHA256(secret, body). The result is included in the X-Forge-Signature: sha256=<hex> request header. Your secret is unique per webhook and is available in Settings → Integrations → Webhooks — click "Reveal" next to the webhook.
Python
import hmac
import hashlib
def verify_forge_signature(
body: bytes,
signature_header: str,
secret: str
) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
received = signature_header.removeprefix("sha256=")
# Use compare_digest to prevent timing attacks
return hmac.compare_digest(expected, received)
# Flask example
@app.route("/forge-webhook", methods=["POST"])
def webhook():
body = request.get_data()
sig = request.headers.get("X-Forge-Signature", "")
if not verify_forge_signature(body, sig, FORGE_SECRET):
abort(401)
payload = request.json
# handle payload...
return "ok"Node.js / TypeScript
import { createHmac, timingSafeEqual } from "crypto"
function verifyForgeSignature(
body: string | Buffer,
signatureHeader: string,
secret: string
): boolean {
const sig = createHmac("sha256", secret)
.update(body)
.digest("hex")
const received = signatureHeader.replace("sha256=", "")
// timingSafeEqual prevents timing-based attacks
return timingSafeEqual(
Buffer.from(sig, "hex"),
Buffer.from(received, "hex")
)
}
// Express example
app.post("/forge-webhook", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-forge-signature"] as string
if (!verifyForgeSignature(req.body, sig, process.env.FORGE_SECRET!)) {
return res.status(401).send("Unauthorized")
}
const payload = JSON.parse(req.body.toString())
// handle payload...
res.send("ok")
})hmac.compare_digest in Python, timingSafeEqual in Node.js). A regular string equality check (===) is vulnerable to timing attacks that can leak the expected signature bit by bit.Rotating your secret
If your secret is ever exposed, delete the webhook from Settings → Integrations → Webhooks and add a new one. A new secret is generated automatically. Update the secret in your server environment and redeploy.