Blockstream Enterprise Custody SDK
Recipes

Policy Configuration

How to define ALLOW, DENY, and REVIEW behavior in the Custody Engine, and how to model policy logic clearly for operational workflows.

Policy configuration is one of the central control surfaces in Blockstream Enterprise Custody. Policies determine whether actions execute immediately, are denied, or become proposals that require review.

Why this page matters

Policies apply far beyond transaction approval. You can use them to govern:

  • spend requests
  • wallet operations
  • user creation or modification
  • role and group management
  • asset-management changes

Evaluation order

Policies are evaluated in descending priority order.

  1. the request arrives
  2. the system checks the highest-priority matching policy first
  3. the first matching policy decides the outcome
  4. if nothing matches, the default behavior is applied

That makes priority design important: broad catch-all policies should usually sit below more specific ones.

The core fields

FieldMeaning
resourceWhich resource path the policy applies to
actionWhich action must match, such as add, edit, or delete
priorityEvaluation order, higher first
expressionCondition checked against the request context
decision_on_trueALLOW, DENY, or REVIEW
review_expressionRequired only for REVIEW; defines the approval rule

Basic example

import { v4 as uuidv4 } from 'uuid'

const policyResult = await broadcastRequest({
  action: 'add',
  resource: '/policies',
  details: {
    plid: uuidv4(),
    name: 'require-approval-for-large-transfers',
    description: 'Transfers over 10000 require manager approval',
    resource: `/wallets/${walletId}/spend-requests`,
    action: 'add',
    priority: 100,
    expression:
      'request.details.tx_type == "send" && request.details.request.recipients[0].amount > 10000',
    decision_on_true: 'REVIEW',
    review_expression: '1 OF managers',
  },
})

Review expressions

Review expressions define who must approve a proposal once a policy returns REVIEW.

Examples:

1 OF managers
2 OF security_team
(1 OF security_team) AND (1 OF compliance_team)

What expressions can inspect

Policy expressions evaluate against the request context. Commonly used inputs include:

  • request
  • action
  • resource
  • caller

In practice, that means you can model rules such as:

  • large transfers require human approval
  • only certain caller groups may initiate issuance
  • only specific recipients are allowed for some wallets
  • asset-management operations require compliance review

Example: multi-tier review

const policyResult = await broadcastRequest({
  action: 'add',
  resource: '/policies',
  details: {
    plid: uuidv4(),
    name: 'multi-tier-approval',
    resource: `/wallets/${walletId}/spend-requests`,
    action: 'add',
    priority: 100,
    expression: 'request.details.tx_type == "issue"',
    decision_on_true: 'REVIEW',
    review_expression: `(1 OF ${securityGroupId}) AND (1 OF ${complianceGroupId})`,
  },
})

Example: whitelist-sensitive transfer review

const restrictedTransferPolicy = await broadcastRequest({
  action: 'add',
  resource: '/policies',
  details: {
    plid: uuidv4(),
    name: 'whitelist-only-transfers',
    resource: '/wallets/*/spend-requests',
    action: 'add',
    priority: 100,
    expression: `
      request.details.tx_type == "send" &&
      getWalletRecipientGroups(request.details.wallet_id)
        .includes("${sendersGroupId}") &&
      request.details.request.recipients
        .map(r => r.recipient.value)
        .every(val => getRecipientWhitelistsByValues([val])
          .includes("${approvedRecipientsWhitelistId}"))
    `,
    decision_on_true: 'REVIEW',
    review_expression: `1 OF ${complianceGroupId}`,
  },
})

Practical policy-design advice

  • keep high-priority policies specific
  • put broad fallback policies lower in the stack
  • make review expressions readable for operations teams
  • separate business-logic policies from emergency lock-down policies

On this page