> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # validate_webhook_signature > Validate a SignalWire webhook signature against the raw request body. [security-overview]: /docs/server-sdks/reference/python/core/security [validate-request]: /docs/server-sdks/reference/python/core/security/validate-request Verify that an incoming webhook was signed by SignalWire with your Signing Key. This is the primary standalone validator for custom (non-`AgentBase`) servers. Pass it the raw, unparsed request body so the signature matches what the platform computed. > **Note** > > The signature value comes from the `X-SignalWire-Signature` request header (or > `X-Twilio-Signature` for Compatibility callbacks). See the > [security overview][security-overview] for the recognized header names. ## **Parameters** **`signing_key`** `str` — required Your Signing Key from the Dashboard, as a UTF-8 string. A `None` or empty value raises `ValueError` — this is a programming error, not a validation failure. --- **`signature`** `str` — required The `X-SignalWire-Signature` header value (or `X-Twilio-Signature` for Compatibility callbacks). A missing or empty value returns `False` without raising. --- **`url`** `str` — required The full URL SignalWire POSTed to — scheme, host, optional port, path, and query. It must match what the platform saw. --- **`raw_body`** `str` — required The raw request body as a UTF-8 string, *before* any JSON or form parsing. Must be a `str`; passing a parsed dict raises `TypeError`. --- ## **Returns** `bool` — `True` if the signature matches, `False` otherwise. ## **Example** ```python {12-17} from fastapi import FastAPI, Request, Response, status from signalwire.core.security import validate_webhook_signature app = FastAPI() SIGNING_KEY = "PSK_your_signing_key" @app.post("/webhook") async def webhook(request: Request): raw_body = (await request.body()).decode("utf-8") signature = request.headers.get("x-signalwire-signature", "") if not validate_webhook_signature( signing_key=SIGNING_KEY, signature=signature, url=str(request.url), raw_body=raw_body, ): return Response(status_code=status.HTTP_403_FORBIDDEN) # Signature is valid — safe to process the request. return {"ok": True} ``` > **Tip** > > If you already have pre-parsed form parameters instead of a raw body, use > [`validate_request`][validate-request], which accepts either form. > Validate a SignalWire webhook signature against the raw request body.