---
name: azion-find-the-waf-score-of-a-blocked-request
description: >-
  Read which internal rules a refused request matched, and what each threat family scored, from Real-Time Events or the GraphQL API.
---

# Find the WAF score of a blocked request

You can read which internal rules matched a request that [Web Application Firewall (WAF)](/en/documentation/platform/firewall/#waf) refused, and what each threat family scored.

The decision leaves nothing in the response, so it is looked up afterwards. The `x-azion-request-id` header on the refusal is what ties it to its event. For the model behind the score, refer to [Scoring and modes](/en/documentation/platform/firewall/waf/scoring-and-modes/#scoring-and-sensitivity).

---

Select the interface you will use. The prerequisites and the lookup below follow that choice.

## Prerequisites

- A workload bound to a firewall that applies a WAF rule set in `Blocking` mode. Refer to [WAF quickstart](/en/documentation/platform/firewall/waf/quickstart/).
- The domain of that workload, which has the form `<id>.map.azionedge.net`.

**Console**

- Access to Azion Console. To sign in, refer to [How to access Azion Console](/en/documentation/guides/platform/account-and-billing/how-to-access-azion-console/).

**API**

- A personal token and `curl`. To create a token, refer to [Personal Tokens](/en/documentation/fundamentals/personal-tokens/).

---

## Capture the request ID

A refused request receives `400`, and it never reaches an origin. Nothing in that response names WAF, the rule that matched, or the score, so the request ID is the only value that finds the event.

To provoke a refusal on your own workload, send an injection-shaped request:

```bash
curl -i "https://<your-workload-domain>/?q=1%27%20OR%20%271%27%3D%271"
```

WAF refuses it:

```text
HTTP/2 400
content-type: text/html
x-azion-request-id: 0123456789abcdef0123456789abcdef
```

Record `x-azion-request-id`. No `x-azion-waf-*` header exists, in this response or any other, so a refusal is not distinguishable from another `400` by headers alone.

The body is Azion's default error page, headed **Bad Request**. A visitor who reports a refusal can read the same value from the **Request ID** row of its **Error Details** block.

---

## Find the event in Real-Time Events

[Real-Time Events](/en/documentation/platform/real-time-events/) holds one row per request, and that row carries the WAF decision.

**Console**

To open the event in Azion Console:

1. **Open Real-Time Events**

   Access [Azion Console](https://console.azion.com/) > **Real-Time Events**.

2. **Select the HTTP Requests tab**

3. **Set the time range**

   In the time range dropdown, select a range that contains the request.

4. **Search for the refused requests**

   In the **Search** field, enter the query below, with the domain of your workload in `host`:

   ```text
   host='<your-workload-domain>' AND waf_attack_action='$BLOCK'
   ```

5. **Select the request in the results list**

The event opens with its WAF fields, among them **WAF Block** and **WAF Learning**.

A second query reaches the same rows from the other side, since a refusal is a `400` whose upstream status is `0`:

```text
host='<your-workload-domain>' AND status='400' AND upstream_status='0'
```

The two queries return similar results, with small variations between them.

**API**

To return the event over the GraphQL API:

1. **Write the query**

   Save the following as `waf-event.json`, with your request ID in `requestIdEq` and a time window around the request in `tsGte` and `tsLt`:

   ```json
   {
     "query": "query { workloadEvents(limit: 3, filter: { tsGte: \"2026-01-01T11:55:00\", tsLt: \"2026-01-01T12:05:00\", requestIdEq: \"0123456789abcdef0123456789abcdef\" }) { ts requestId host requestUri status upstreamStatus wafBlock wafMatch wafLearning wafScore wafAttackFamily wafAttackAction wafEvheaders wafTotalBlocked wafTotalProcessed } }"
   }
   ```

2. **Send the query**

   Replace `[TOKEN VALUE]` with your personal token:

   ```bash
   curl --request POST \
     --url https://api.azion.com/v4/events/graphql \
     --header 'Accept: application/json' \
     --header 'Authorization: Token [TOKEN VALUE]' \
     --header 'Content-Type: application/json' \
     --data @waf-event.json
   ```

3. **Read the event**

   The call answers `200`, and the row carries the whole decision:

   ```json
   {
     "ts": "2026-01-01T12:00:00Z",
     "requestId": "0123456789abcdef0123456789abcdef",
     "host": "<your-workload-domain>",
     "requestUri": "/?q=1%27%20OR%20%271%27%3D%271",
     "status": 400,
     "upstreamStatus": 0,
     "wafBlock": "1",
     "wafMatch": "0:1009:ARGS:q,1:1013:ARGS:q",
     "wafLearning": "0",
     "wafScore": "0:$SQL:18,1:$XSS:32",
     "wafAttackFamily": "$SQL,$XSS",
     "wafAttackAction": "$BLOCK",
     "wafTotalBlocked": 0,
     "wafTotalProcessed": 0,
     "wafEvheaders": "<base64>"
   }
   ```

`wafEvheaders` is base64. Decoded, it carries the headers of the request that was refused.

> **Note**
>
> The endpoint is `/v4/events/graphql`, and the dataset is `workloadEvents`. The older `/events/graphql` path answers `204` with an empty body, and no `httpEvents` or `wafEvents` dataset exists. The WAF fields on `workloadEvents` are camelCase: `wafBlock`, not `waf_block`.

---

## Read the match and the score

`wafMatch` and `wafScore` carry the decision. Both are strings holding one entry per match, separated by commas, and both read `-` on a request WAF did not act on.

`wafMatch` is shaped `<index>:<ruleId>:<zone>:<varName>`. In `0:1009:ARGS:q,1:1013:ARGS:q`, the internal rules `1009` and `1013` each matched the `q` argument in the `ARGS` zone. Those two, rather than a single injection rule, are what `1' OR '1'='1` fires. For what each id detects, refer to [WAF Rule Sets](/en/documentation/platform/firewall/waf/rules-set/#internal-rules).

`wafScore` is shaped `<index>:$<family>:<score>`, and it is **one score per threat family**, never one number for the request. In `0:$SQL:18,1:$XSS:32`, the SQL injection family scores `18` and the cross-site scripting family scores `32`. A family blocks when its score reaches the threshold of the sensitivity level set for it: both families above are set to `medium`, whose threshold is `16`. The thresholds are listed in [WAF Rule Sets](/en/documentation/platform/firewall/waf/rules-set/#sensitivity-levels).

No source states an upper bound for a score, so a score carries no percentage reading. An internal rule that belongs to no scored family reports `wafScore` as a bare family name, such as `$OTHERS`.

The remaining fields narrow the reading. `wafBlock` reads `1` when the request was refused. `wafLearning` reads `1` when the rule ran in `Logging` mode, which records the match and serves the request. `wafAttackFamily` and `wafAttackAction` summarize the same event as `$SQL,$XSS` and `$BLOCK`. Azion Console renders these values under labels, among them **WAF Block** and **WAF Learning**.

`wafTotalProcessed` and `wafTotalBlocked` read `0` on every per-request row, including a row for a request that was refused. They are aggregate counters, so they answer nothing about one request.

---

## Next steps

- [Scoring and modes](/en/documentation/platform/firewall/waf/scoring-and-modes.md): The path a request travels, the scoring model, and what each mode does.
- [WAF Rule Sets](/en/documentation/platform/firewall/waf/rules-set.md): The eight threat families, the five sensitivity levels, and the internal rules behind every match.
- [Tune a WAF rule set](/en/documentation/guides/application-security/firewall-and-waf/tune-waf.md): Read what a rule set matched and turn a false positive into an exception.
- [Troubleshoot Firewall](/en/documentation/platform/firewall/troubleshooting.md#waf): What to do when a legitimate request is refused, or an attack-shaped one is served.
