Skip to main content
This guide covers how to manage policies and handle approval workflows using the Dfns API and SDK.

Prerequisites

  • Service account or authenticated user with policy management permissions
  • Dfns SDK installed and configured
  • Understanding of policies

Creating a policy

Create a policy that requires approval for large transactions:
import { DfnsApiClient } from '@dfns/sdk'

const dfns = new DfnsApiClient({
  baseUrl: 'https://api.dfns.io',
  // Your signer configuration
})

const policy = await dfns.policies.createPolicy({
  body: {
    name: 'Large Transaction Approval',
    activityKind: 'Wallets:Sign',
    rule: {
      kind: 'TransactionAmountLimit',
      configuration: {
        limit: '100000',
        currency: 'USD'
      }
    },
    action: {
      kind: 'RequestApproval',
      approvalGroups: [
        {
          name: 'Treasury',
          quorum: 2,
          approvers: {
            userId: {
              in: ['us-xxx-1', 'us-xxx-2', 'us-xxx-3']
            }
          },
          serviceAccountsCanApprove: false, // set to true to allow service accounts to vote (requires staff activation)
        }
      ],
      autoRejectTimeout: 86400 // 24 hours
    },
    status: 'Active'
  }
})

console.log('Policy ID:', policy.id)

Policy rule types

Transaction amount limit

Trigger when a single transaction exceeds a threshold:
rule: {
  kind: 'TransactionAmountLimit',
  configuration: {
    limit: '100000',
    currency: 'USD'
  }
}

Transaction amount velocity

Trigger when total transaction value exceeds a limit within a time window:
rule: {
  kind: 'TransactionAmountVelocity',
  configuration: {
    limit: '500000',
    currency: 'USD',
    timeWindow: 86400 // 24 hours in seconds
  }
}

Transaction count velocity

Trigger when transaction count exceeds a limit within a time window:
rule: {
  kind: 'TransactionCountVelocity',
  configuration: {
    limit: 100,
    timeWindow: 86400
  }
}

Recipient whitelist

Trigger when recipient is NOT in the whitelist:
rule: {
  kind: 'TransactionRecipientWhitelist',
  configuration: {
    addresses: [
      '0x...address1',
      '0x...address2'
    ]
  }
}

Always trigger

Trigger on all matching activities:
rule: {
  kind: 'AlwaysTrigger',
  configuration: {}
}

Policy actions

Request approval

Require human approval before proceeding:
action: {
  kind: 'RequestApproval',
  approvalGroups: [
    {
      name: 'Finance',
      quorum: 1,
      approvers: {
        userId: { in: ['us-xxx-1', 'us-xxx-2'] }
      }
    }
  ],
  autoRejectTimeout: 86400
}

Block

Block the activity entirely:
action: {
  kind: 'Block'
}

Policy filters

Apply policies to specific wallets using tags:
const policy = await dfns.policies.createPolicy({
  body: {
    name: 'Treasury Policy',
    activityKind: 'Wallets:Sign',
    rule: {
      kind: 'TransactionAmountLimit',
      configuration: { limit: '50000', currency: 'USD' }
    },
    action: {
      kind: 'RequestApproval',
      approvalGroups: [/* ... */]
    },
    filters: {
      walletTags: {
        hasAny: ['treasury']
      }
    },
    status: 'Active'
  }
})

Listing policies

Get all policies:
const policies = await dfns.policies.listPolicies()

for (const policy of policies.items) {
  console.log(`${policy.name}: ${policy.status}`)
}

Updating a policy

Update an existing policy:
await dfns.policies.updatePolicy({
  policyId: 'pl-xxx-xxx',
  body: {
    name: 'Updated Policy Name',
    status: 'Active'
  }
})

Deleting a policy

Delete a policy:
await dfns.policies.deletePolicy({
  policyId: 'pl-xxx-xxx'
})

Working with approvals

Listing pending approvals

Get approvals awaiting action:
const approvals = await dfns.policyApprovals.listApprovals({
  query: {
    status: 'Pending'
  }
})

for (const approval of approvals.items) {
  console.log(`Approval ${approval.id}: ${approval.activity.kind}`)
}

Getting approval details

Get details for a specific approval:
const approval = await dfns.policyApprovals.getApproval({
  approvalId: 'pa-xxx-xxx'
})

console.log('Activity:', approval.activity)
console.log('Triggered policies:', approval.triggeredPolicies)
console.log('Decisions so far:', approval.decisions)

Submitting an approval decision

Approve or reject a pending approval:
// Approve
await dfns.policyApprovals.createApprovalDecision({
  approvalId: 'pa-xxx-xxx',
  body: {
    value: 'Approved',
    reason: 'Verified transaction details with finance team'
  }
})

// Reject
await dfns.policyApprovals.createApprovalDecision({
  approvalId: 'pa-xxx-xxx',
  body: {
    value: 'Rejected',
    reason: 'Recipient address not verified'
  }
})
The user or service account submitting the decision must be listed in one of the approval groups defined in the policy. The initiator cannot approve their own transactions by default. Service accounts can submit decisions when serviceAccountsCanApprove is enabled on the group.

Webhook notifications

Subscribe to policy events:
await dfns.webhooks.createWebhook({
  body: {
    url: 'https://your-app.com/webhooks/dfns',
    events: [
      'policy.approval.pending',
      'policy.approval.resolved',
      'policy.triggered'
    ],
    status: 'Enabled'
  }
})
Handle approval events:
app.post('/webhooks/dfns', async (req, res) => {
  const event = req.body

  switch (event.kind) {
    case 'policy.approval.pending':
      // Notify approvers
      await notifyApprovers(event.data)
      break
    case 'policy.approval.resolved':
      // Approval completed (approved or rejected)
      await handleApprovalResolved(event.data)
      break
  }

  res.status(200).send('OK')
})

Example: Multi-tier approval

Create a policy with multiple approval tiers:
// Tier 1: Operations approval for $10k+
await dfns.policies.createPolicy({
  body: {
    name: 'Operations Approval',
    activityKind: 'Wallets:Sign',
    rule: {
      kind: 'TransactionAmountLimit',
      configuration: { limit: '10000', currency: 'USD' }
    },
    action: {
      kind: 'RequestApproval',
      approvalGroups: [{
        name: 'Operations',
        quorum: 1,
        approvers: { userId: { in: ['ops-user-1', 'ops-user-2'] } }
      }]
    },
    status: 'Active'
  }
})

// Tier 2: Finance approval for $100k+
await dfns.policies.createPolicy({
  body: {
    name: 'Finance Approval',
    activityKind: 'Wallets:Sign',
    rule: {
      kind: 'TransactionAmountLimit',
      configuration: { limit: '100000', currency: 'USD' }
    },
    action: {
      kind: 'RequestApproval',
      approvalGroups: [{
        name: 'Finance',
        quorum: 2,
        approvers: { userId: { in: ['finance-1', 'finance-2', 'finance-3'] } }
      }]
    },
    status: 'Active'
  }
})

Automated approvals with service accounts

Service accounts can participate in approval groups, enabling automated approval workflows. For example, a compliance service that checks transactions against internal rules before approving.
This feature requires activation by Dfns staff on your organization. Contact support to enable it.

Setting up the policy

Create a policy with a service account in an approval group. The service account must be explicitly listed in approvers:
const policy = await dfns.policies.createPolicy({
  body: {
    name: 'Automated Compliance Check',
    activityKind: 'Wallets:Sign',
    rule: {
      kind: 'TransactionAmountLimit',
      configuration: {
        limit: '10000',
        currency: 'USD'
      }
    },
    action: {
      kind: 'RequestApproval',
      approvalGroups: [
        {
          name: 'Compliance',
          quorum: 1,
          approvers: {
            userId: {
              in: ['us-sa-xxx', 'us-xxx-1', 'us-xxx-2'] // service account + human fallbacks
            }
          },
          serviceAccountsCanApprove: true,
        }
      ],
      autoRejectTimeout: 86400
    },
    status: 'Active'
  }
})
The group has a quorum of 1 and includes both the service account and human approvers. The service account evaluates the transaction first. If it approves, the quorum is met and the transaction proceeds. If the service account can’t approve, it notifies a human to review and approve instead. Either way, every approval decision is recorded with full accountability.
A service account rejection denies the entire approval. Design your automation to abstain rather than reject when unsure. Notify a human approver and let them make the call within Dfns. This preserves the audit trail and non-repudiation.

Processing approvals as a service account

Use webhooks to react to pending approvals in real time. When a policy.approval.pending event arrives, the service account evaluates the transaction. If it passes, the service account approves. Otherwise, it notifies a human to review:
app.post('/webhooks/dfns', async (req, res) => {
  const event = req.body

  if (event.kind !== 'policy.approval.pending') {
    return res.status(200).send('OK')
  }

  const approval = await dfns.policyApprovals.getApproval({
    approvalId: event.data.approvalId,
  })

  // Run your approval logic (e.g. sanctions screening, internal limits)
  const decision = await evaluateTransaction(approval.activity)

  if (decision.approved) {
    await dfns.policyApprovals.createApprovalDecision({
      approvalId: approval.id,
      body: {
        value: 'Approved',
        reason: decision.reason,
      },
    })
  } else {
    // Don't reject: let a human approver review instead
    await notifyHumanApprover(approval, decision.reason)
  }

  res.status(200).send('OK')
})

Policies API

Complete API reference

Approvals API

Approval management API

Policies concept

Understanding policies

Webhooks

Event notifications
Last modified on March 26, 2026