# Policy best practices

This page provides recommended patterns for planning, configuring, and maintaining transaction policies.

## Planning your policy strategy

**Start restrictive, then expand.** Begin with conservative limits and increase them as you understand your transaction patterns. It's easier to raise limits than to recover from unauthorized transactions.

**Layer your limits.** Combine multiple limit types for defense in depth:

| Limit type | Purpose |
|  --- | --- |
| Per transaction | Prevents large single transactions |
| Rolling duration | Prevents rapid accumulation of smaller transactions |
| Max total value | Caps the total amount that can leave the wallet over its lifetime |


For example, a hot wallet holding ETH might use three policies together:

```text
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "50", "duration": "86400s"}
{"limitType": "CONSTANT", "symbol": "ETH", "limitQty": "500"}
```

These policies set a 10 ETH per-transaction limit, a 50 ETH rolling 24-hour limit, and a 500 ETH total limit on the transactions that limit rules evaluate. Sweeps, passkey transfers, and some signing actions aren't evaluated against limits. See [Operations that bypass policies](/products/wallet/governance-and-controls/policies#operations-that-bypass-policies). A policy with matchers that applies to a transaction also replaces these base policies for that transaction. Totals count the asset's outbound transactions that aren't rejected or failed (sweeps and passkey transfers aren't counted), including pending ones and ones made before you created the policy, so a new Max total value policy can already be at its limit when it becomes active. A rolling or Max total value policy with matchers totals only the transactions that its matchers select, for example only transfers to one counterparty or only transactions from one API credential, not the wallet's whole outflow. See [Limit types](/products/wallet/governance-and-controls/policies/policy-reference#limit-types).

**Enable approval groups.** Without approval groups, anyone with `policy:create` or `keylimit:create` API permissions can instantly enable policies. See [Approvals](/products/wallet/governance-and-controls/approvals) to add human oversight.

**Avoid common mistakes:**

- Don't use a limit of zero. A policy rule with a zero threshold blocks every transfer of that asset above zero, and the wallet limits endpoints reject a limit of zero. They reject negative limits too.
- Don't rely on base policies for cross-chain or sign-for transactions. Base policies never apply to cross-chain transactions, and a sign-for transaction needs a policy with a matching `SIGN_FOR` matcher. See [Matchers](/products/wallet/governance-and-controls/policies/policy-reference#matchers).
- Don't rely on a single high-limit policy without matchers—it provides minimal protection.
- Don't rely solely on per-transaction limits—combine with rolling limits to catch accumulation attacks.
- Don't leave assets without policies. Ripple Custody (Wallet) rejects transfers of those assets with `PAL006.005`.


## Configuration examples

The following examples show common policy configurations for different wallet types.

**Hot wallet** – Process many small transactions with tight limits. A transaction within the limits still waits for approval if the wallet or organization has a transaction approval group:

```text
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "1"}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "50", "duration": "86400s"}
```

Multiple rolling policies
You can create multiple rolling-duration policies with different windows by using different matchers. Without matchers, only one rolling-duration policy per asset is allowed.

**Cold storage** – Hold long-term reserves with strict, manually-approved withdrawals. Use low per-transaction limits and combine with mobile-only MPC quorums and transaction approval groups.

**Treasury wallet** – Handle large transfers to trusted counterparties while restricting other destinations:

```text
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "1000", "matchers": [{"type": "COUNTERPARTY_ID", "values": ["trusted-uuid"]}]}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}
```

**Role-based limits** – Use `USER` matchers to set different limits for different team members:

```text
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "5", "matchers": [{"type": "USER", "values": ["ops-user-uuid"]}]}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "100", "matchers": [{"type": "USER", "values": ["finance-user-uuid"]}]}
```

**API credential isolation** – Use `API_CREDENTIAL` matchers to limit each integration's authority:

```text
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "1", "matchers": [{"type": "API_CREDENTIAL", "values": ["trading-bot-uuid"]}]}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "100", "duration": "86400s", "matchers": [{"type": "API_CREDENTIAL", "values": ["settlement-uuid"]}]}
```

## Ongoing operations

**Review policies regularly.** Schedule periodic reviews to check whether limits are still appropriate for current transaction volumes, whether unused policies should be removed, and whether new assets need policies.

**Monitor transaction rejections.** Frequent rejections may indicate limits set too low, attempted unauthorized access, or misconfigured policies. Use the transaction list to filter by `REJECTED` status and review the rejection reasons. In the console's **Status** filter, `REJECTED` appears in the **Violation** group. A policy system error ends the transaction as `FAILED`, which the **Failed** group lists, so monitor both statuses.

**Document your policy strategy.** Record why each policy exists, who approved it, and when it was last reviewed. This helps during audits and when onboarding new team members.

**Upgrade to scoped matchers.** If you have existing policies without matchers, you can add more granular policies alongside them. When a policy with matchers matches a transaction, it replaces the policies without matchers for that transaction, including rolling and max-total-value policies, even if their limits are lower. A scoped policy can therefore loosen a base limit. The console **Confirm** step warns you when matching policies are overridden. Add matching rolling or lifetime policies with the same matchers if you want those caps to keep applying:

```text
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "100", "matchers": [{"type": "COUNTERPARTY_ID", "values": ["trusted-uuid"]}]}
```

**Replace policies carefully.** Policies are immutable. A new policy that differs from an existing one only in amount or window counts as a duplicate, and Ripple Custody (Wallet) rejects it until you delete the old policy. To change a limit, delete the old policy, wait until the deletion completes, and then create the new one. Until the new policy takes effect, Ripple Custody (Wallet) rejects transfers of that asset that no other policy covers, with `PAL006.005`. Creating the new policy first works only when it differs in matchers or limit type. See [Manage policies via the console](/products/wallet/governance-and-controls/policies/manage-policies#modify-a-policy-via-the-console) for the step-by-step process.

## Troubleshooting

| Symptom | Cause | Solution |
|  --- | --- | --- |
| Transaction rejected with `PAL006.005`: `No transaction limit configured for <asset> on wallet '<wallet name>'. Add a transaction limit for this asset.` or `Transaction limits for <asset> on wallet '<wallet name>' exist but are not active. Check limit approval status.` | No active policy exists for the asset | Create a limit rule for the asset. Verify status is `RULE_ENABLED`. |
| Transaction rejected with `PAL006.006`, `PAL006.007`, or `PAL006.008`, for example `Transaction of <qty> <asset> exceeds per-transaction limit of <limit> <asset>.` | Transaction amount exceeds a per-transaction (`PAL006.006`), lifetime (`PAL006.007`), or rolling (`PAL006.008`) limit | Reduce amount, or delete and recreate policy with higher limit. For rolling limits, wait for older transactions to roll off. |
| Transaction rejected and several policies apply to it | One or more of the applicable limits rejected it. The rejection carries the message of one of them, such as `PAL006.006`, not a generic message | Read the message to see which limit the transaction exceeded. List all policies for the wallet and check which ones match your transaction. |
| API error `PAL006.039`: "an equivalent policy rule already exists" (or `PAL006.023`: "limit policy already exists" from the wallet limits endpoints) | Rule with same wallet, kind, trigger, asset, limit type, and matchers exists (for step-up approval rules, the same threshold too) | Delete existing policy first, or add different matchers to create a distinct policy. |
| API error `PAL006.016`: "the action you are attempting to perform is not allowed" | Attempting to delete a policy that is pending creation or deletion approval | Wait for approvers to approve or reject the policy before deleting it. |
| API error `PAL000.002`: "unauthorized request" | Missing API scope or vault access | Verify credentials have the `policy:*` permissions (policy rules API) or `keylimit:*` permissions (wallet limits endpoints) as needed. |
| Policy shows `active: false` | Policy awaiting approval | Check if status is `RULE_CREATION_APPROVAL_PENDING`. Ask approvers to authorize. |
| Transactions passing that should be blocked | Policy matchers don't match transaction | Review matchers—policy only applies when **all** matchers match. |
| Policy stuck in pending | Not enough approvers responded | Ask additional approvers. If timeout expires, create a new policy. |
| Policy rejected unexpectedly | Approval threshold impossible to meet | Too many approvers skipped. Create a new policy and coordinate approvers. |
| `PAL006.039` or `PAL006.023` when creating a replacement policy | Rejected policy still counts for uniqueness | A policy in `RULE_REJECTED` status still occupies its uniqueness slot. Deleted policies don't. Delete the rejected policy, then create the replacement. |
| Transactions blocked after deleting a counterparty or address | Policy has orphaned matcher reference | The policy still exists. If the deleted entity was the only value in its matcher, no transaction can match the policy. If the matcher lists other values, the policy still matches those. Ripple Custody (Wallet) checks transactions that the policy no longer matches against your other applicable policies, and rejects them with `PAL006.005` only if none applies. To replace an orphaned policy, delete it and create a new one. See [Policy reference](/products/wallet/governance-and-controls/policies/policy-reference#combining-matchers) for details. |


Known display issue
Rolling limit rejection messages round the window down. A window under 1 hour shows as "0-hour", a window from 24 to 47 hours shows as "24-hour", and longer windows show whole days, such as "7-day" or "30-day". Enforcement uses the exact duration that you configured.

## Related documentation

- [Policies](/products/wallet/governance-and-controls/policies) – Core concepts and lifecycle
- [Policy reference](/products/wallet/governance-and-controls/policies/policy-reference) – Complete API reference
- [Manage policies via the console](/products/wallet/governance-and-controls/policies/manage-policies) – Step-by-step guide
- [Approvals](/products/wallet/governance-and-controls/approvals) – Configure approval groups