Skip to main content
This guide explains how to monitor transaction status after submitting transfers or broadcasts through Dfns.

Transaction lifecycle

When you submit a transaction via the Transfer API or Broadcast API, it goes through these states:
StatusDescription
PendingTransaction created, awaiting policy approval or signing
BroadcastedSigned and sent to the network mempool
ConfirmedSuccessfully included in a block
FailedBroadcast succeeded but execution failed on-chain
RejectedBlocked by policy or approval rejected

Polling

Simple approach for occasional status checks. Query the transfer or transaction until it reaches a terminal state.
async function waitForConfirmation(
  walletId: string,
  transferId: string,
  maxAttempts = 60,
  intervalMs = 5000
): Promise<Transfer> {
  for (let i = 0; i < maxAttempts; i++) {
    const transfer = await dfnsClient.wallets.getTransfer({
      walletId,
      transferId,
    })

    if (transfer.status === 'Confirmed') {
      return transfer
    }

    if (transfer.status === 'Failed' || transfer.status === 'Rejected') {
      throw new Error(`Transfer ${transfer.status}: ${transfer.reason}`)
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs))
  }

  throw new Error('Timeout waiting for confirmation')
}

// Usage
const transfer = await dfnsClient.wallets.transferAsset({
  walletId,
  body: { kind: 'Native', to: recipient, amount: '1000000000000000000' },
})

const confirmed = await waitForConfirmation(walletId, transfer.id)
console.log('Transaction hash:', confirmed.txHash)
For production systems, use webhooks to receive status updates in real-time instead of polling.

Relevant events

EventWhen it fires
wallet.transfer.requestedTransfer created
wallet.transfer.broadcastedTransfer sent to mempool
wallet.transfer.confirmedTransfer confirmed on-chain
wallet.transfer.failedTransfer failed
wallet.transfer.rejectedTransfer rejected by policy
wallet.transaction.requestedBroadcast transaction created
wallet.transaction.broadcastedTransaction sent to mempool
wallet.transaction.confirmedTransaction confirmed on-chain
wallet.transaction.failedTransaction failed
wallet.transaction.rejectedTransaction rejected by policy
wallet.blockchainevent.detectedIncoming deposit or other on-chain event
See Webhook Events for the complete list and event data schemas.

Basic handler

app.post('/webhooks/dfns', express.json(), (req, res) => {
  // Always respond quickly with 200
  res.status(200).send('OK')

  const event = req.body

  switch (event.kind) {
    case 'wallet.transfer.confirmed':
      const { transferRequest } = event.data
      console.log(`Transfer ${transferRequest.id} confirmed`)
      console.log(`Tx hash: ${transferRequest.txHash}`)
      // Update your database, notify user, etc.
      break

    case 'wallet.transfer.failed':
      const { transferRequest: failed } = event.data
      console.error(`Transfer ${failed.id} failed`)
      // Alert, retry logic, etc.
      break

    case 'wallet.blockchainevent.detected':
      const { blockchainEvent } = event.data
      if (blockchainEvent.direction === 'In') {
        console.log(`Received ${blockchainEvent.value} deposit`)
      }
      break
  }
})
Always respond with 200 quickly, even if processing takes time. Use a queue for async processing. See Webhooks best practices for details.

Setup

  1. Create your webhook endpoint (see Local development for testing)
  2. Create a webhook via API or dashboard
  3. Subscribe to the events you need
  4. Verify webhook signatures to ensure events are from Dfns

Detecting deposits

Use the wallet.blockchainevent.detected event to detect incoming transfers:
case 'wallet.blockchainevent.detected':
  const { blockchainEvent } = event.data

  if (blockchainEvent.direction === 'In') {
    switch (blockchainEvent.kind) {
      case 'NativeTransfer':
        console.log(`Native deposit: ${blockchainEvent.value}`)
        break
      case 'Erc20Transfer':
        console.log(`ERC-20 deposit: ${blockchainEvent.value}`)
        console.log(`Token: ${blockchainEvent.contract}`)
        break
      // Handle other transfer types...
    }
  }
  break
Deposit detection (wallet.blockchainevent.detected) is only available for Tier-1 networks.

Confirmation times

Different networks have different finality characteristics:
NetworkAverage confirmationNotes
Ethereum~12 secondsTrue finality ~15 min
Polygon~2 secondsCheckpoints to L1
Arbitrum~0.5 secondsDepends on L1 posting
Solana~0.4 secondsNear-instant
Bitcoin~10 minutes6 blocks for high confidence
Dfns marks transactions as Confirmed when included in a block. For high-value transactions on chains with probabilistic finality, you may want additional application-level confirmation tracking.

Handling failures

Rejected by policy

if (transfer.status === 'Rejected') {
  console.log('Rejected:', transfer.reason)
  // e.g., "Blocked by policy: TransactionAmountLimit exceeded"
}
Solutions:
  • Check your policy configuration
  • Request approval if policy requires it
  • Adjust transaction parameters

Failed on-chain

if (transfer.status === 'Failed') {
  console.log('Failed:', transfer.reason)
  // e.g., "execution reverted: insufficient balance"
}
Common causes:
  • Insufficient balance (including gas)
  • Smart contract revert
  • Nonce issues
  • Gas limit too low