> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Validate a Screening Policy

> Confirm a screening policy does what you intended: test in a separate environment, screen a test address, read the event, and review past alerts and blocks with the Events API.

This page covers how to validate a change safely and how to check what a policy has done. There is no dry run. A policy is evaluated only after you save it, and a saved policy is live in its environment within 60 seconds. The check has three parts: screen an address whose expected result you know, confirm that the matching event arrived, and review the alerts and blocks your policy has produced so far.

Run the requests on this page from your server, never from a browser.

## Prerequisites

* A [screening policy](/docs/policies/configure-a-screening-policy) saved at least 60 seconds ago.
* An API token. Screening needs the `environment.settings.read` permission and reading events needs `environment.events.read`. See [API token permissions](/docs/platform/dashboard/api-token-permissions).

## Validate in a separate environment first

Policies belong to one environment, so a sandbox environment gives you a place to save and test a change before it reaches your live users. Validate the change there, then make the same change in your live environment.

* Sandbox needs a screening provider key stored on the environment, and screening policies must be enabled for it. See the [prerequisites](/docs/policies/configure-a-screening-policy#prerequisites) for configuring a policy. You can store the same key you use in live, which makes the provider return the same results for the same address in both environments.
* Make the identical change in the live environment by hand in the editor, or by reading the policy from sandbox and writing the same rules to live through the [API](/docs/policies/screening-policy-rules#writing-a-policy). Address exceptions belong to the provider in each environment, so add them again in live.
* Confirm the result in live afterward with the checks below. If sandbox uses a different provider key than live, the provider can return different results for the same address.

If you can't use a separate environment, limit what a mistake can do. Save a new rule with the action **Alert** and review the alerts before you switch it to **Block**, because an alert never blocks.

## Screen a test address

`GET /environments/{environmentId}/wallet/sanctions` is the same screening as `GET /sdk/{environmentId}/wallet/sanctions`, described in [Screening policy rules](/docs/policies/screening-policy-rules#screening-an-address-yourself), called with an API token. Use `scope` to choose the sign-in policy or the transaction policy, and screen once for each policy you edited. Send `amountUsd` and `asset` to exercise an amount threshold:

```bash theme={"system"}
curl -G \
"https://app.dynamicauth.com/api/v0/environments/<environment_id>/wallet/sanctions" \
-H "Authorization: Bearer <your_token>" \
--data-urlencode "walletAddress=<address>" \
--data-urlencode "chain=EVM" \
--data-urlencode "scope=TRANSACTION" \
--data-urlencode "amountUsd=5000" \
--data-urlencode "asset=USDC"
```

```json theme={"system"}
{
  "walletAddress": "<address>",
  "chain": "EVM",
  "isBlocked": false,
  "action": "alert"
}
```

The response `action` is `allow`, `alert`, or `block`, and an `action` of `alert` leaves `isBlocked` false. Screening an address through this endpoint emits the same events as any other surface, with an `origin` of `api`. Compare the result with what you expect:

| Outcome | Response `action` | Event | `reason.matched` |
| - | - | - | - |
| Blocked by one of your rules | `block` | `wallet.addressScreening.blocked` | `customer` |
| Blocked by an address exception | `block` | `wallet.addressScreening.blocked` | `address` |
| Blocked by the sanctions baseline | `block` | `wallet.sanctions.blocked` | Not applicable |
| Alerted by one of your rules | `alert` | `wallet.addressScreening.alerted` | `customer` |
| Allowed or not screened | `allow` | None | Not applicable |

Which address you test with depends on what you are validating.

* **The simplest check: an address exception.** Add an [address exception](/docs/policies/configure-a-screening-policy#6-except-individual-addresses) that blocks a wallet you control, set **Applies to** to the policy you are testing, and screen that wallet. You get a block with `reason.matched` of `address` and a `wallet.addressScreening.blocked` event, without depending on any provider data or affecting anyone else. This confirms that screening and event delivery work end to end, not that your rules match. Remove the exception when you finish.
* **A rule, in sandbox.** If you have a test address with transaction history that your provider returns categories for, save a rule in sandbox that blocks one of the categories the address falls under, then screen the address and confirm the block. Whether an address matches a rule depends on what your screening provider returns for it, and that can differ between provider accounts, so use an address you have already seen your provider flag in that category, at or above the rule's risk level.
* **An amount threshold.** Pass `amountUsd` and `asset` in the request. A sign-in has no amount, so a threshold with an amount cannot be tested on the **Sign-in** policy.

A provider response is reused for 24 hours and re-evaluated against your current policy. After you edit a rule, screen the same address again to see the new verdict, with no new provider charge.

## Confirm the event

An alert or a block emits an event, and an allowed address emits none. The event carries a `reason` that names the layer that decided. For a match on one of your rules, `reason.rule` is the rule exactly as it was evaluated, so you can confirm that the rule you intended decided and not another one above it. This is how you catch the overlap described in [put the rules in order](/docs/policies/configure-a-screening-policy#5-put-the-rules-in-order). See [Policy and screening webhooks](/docs/embedded-wallets/mpc/policies/webhooks#screening-verdicts) for every field.

<Warning>
  A missing event does not show that the address was evaluated. An address that is allowed, an address skipped by **When to screen**, and a screen where the provider call failed all emit nothing, and screening fails open. If a rule you expected to fire stays silent, check **When to screen** and the rule order before you change the rule.
</Warning>

## Review past alerts and blocks

Dynamic records the events whether or not a webhook subscribes to them, and the Events API returns them, so you can review what a policy has done without having been listening at the time. `GET /environments/{environmentId}/events` returns the events your policies emitted. Send both dates to cover more than one day:

```bash theme={"system"}
curl -G \
"https://app.dynamicauth.com/api/v0/environments/<environment_id>/events" \
-H "Authorization: Bearer <your_token>" \
--data-urlencode "resourceType=wallet.addressScreening" \
--data-urlencode "startDate=2026-10-01" \
--data-urlencode "endDate=2026-10-07"
```

The response is a page of events and a `cursor`. Each event has the `data` of the matching webhook event, described in [Policy and screening webhooks](/docs/embedded-wallets/mpc/policies/webhooks#screening-verdicts):

```json theme={"system"}
{
  "cursor": "<cursor>",
  "data": [
    {
      "eventId": "6f1c0f2a-9d3b-4f0e-8a21-5b7c4e91d0aa",
      "environmentId": "123e4567-e89b-12d3-a456-426614174000",
      "environmentName": "live",
      "eventName": "wallet.addressScreening.alerted",
      "timestamp": "2026-10-06T14:30:59.210Z",
      "data": {
        "walletAddress": "<address>",
        "chain": "ethereum",
        "origin": "transactionSigning",
        "action": "alerted",
        "reason": { "provider": "trm-wallet-screening", "matched": "customer", "revision": 8, "category": "Mixer" }
      },
      "context": {}
    }
  ]
}
```

* **Filter by event name.** `resourceType` matches the start of the event name. `wallet.addressScreening`, as in the request above, returns both your alerts and your blocks. Use `wallet.addressScreening.alerted` for alerts alone, `wallet.sanctions` for blocks by the sanctions baseline, and `admin.addressScreening.policy` for changes to your policies, so you can line a change in behavior up with the edit that caused it.
* **Choose the days.** `startDate` and `endDate` take UTC dates in the form `2026-10-07`. A request without `startDate` returns only today's events, and a request with `startDate` and no `endDate` returns only that day, so send both to cover a range. `startDate` can be at most 30 days ago and a range can span at most 30 days. Events are kept for 90 days in live environments and 30 days in sandbox, but the Events API does not read further back than 30 days.
* **Page through the results.** A page holds 50 events, newest first. Send the `cursor` from a response as the `cursor` query parameter of your next request, and stop when a response has none.
* **Filter on the payload yourself.** Only `resourceType` and the dates filter the results. To narrow by `origin`, `chain`, `walletAddress`, or `reason.category`, read each event's `data` and filter in your own code.
* **Expect alerts and blocks only.** An allowed address emits no event, so the history never lists a screen that passed.

Three checks to verify your policy is working as intended:

* **Run a new rule as an alert first.** Set the action to **Alert**, let real traffic run for a while, then read the `wallet.addressScreening.alerted` events. If the rule alerts on addresses you did not expect, narrow it before you switch it to **Block**.
* **Confirm a change took effect.** After a save, events carry the policy's new `reason.revision`. Compare it with the `revision` the policy read returns. Events that still carry the old value were decided before the change went live.
* **Look for a block you did not intend.** A block on a legitimate address is visible as a `wallet.addressScreening.blocked` event with the rule that caused it in `reason.rule`. Use `reason.matched` to tell a rule from an address exception.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.