Dfns API Documentation
  • 👋Welcome
  • Getting Started
    • Onboarding to Dfns
    • Dfns Environments
    • Core API Objects
    • Supported Assets
    • Postman
    • Dfns SDKs
    • Dashboard Videos
  • API Docs
    • Introduction
    • Authentication
      • Delegated Authentication
        • Delegated Registration
        • Delegated Registration Restart
        • Delegated Login
      • User Action Signing
        • Create User Action Signature Challenge
        • Create User Action Signature
      • Registration
        • Create User Registration Challenge
        • Complete User Registration
        • Complete End User Registration with Wallets
        • Resend Registration Code
        • Social Registration
      • Login
        • Create User Login Challenge
        • Complete User Login
        • Social Login
        • Logout
        • Send Login Code
      • Users
        • List Users
        • Create User
        • Get User
        • Activate User
        • Deactivate User
        • Archive User
      • Service Accounts
        • List Service Accounts
        • Create Service Account
        • Get Service Account
        • Update Service Account
        • Activate Service Account
        • Deactivate Service Account
        • Archive Service Account
      • Applications
        • List Applications
        • Create Application
        • Create Server-Signed Application
        • Get Application
        • Update Application
        • Activate Application
        • Deactivate Application
        • Archive Application
      • Personal Access Tokens
        • List Personal Access Tokens
        • Create Personal Access Token
        • Get Personal Access Token
        • Update Personal Access Token
        • Activate Personal Access Token
        • Deactivate Personal Access Token
        • Archive Personal Access Token
      • Credentials
        • Credentials Overview
        • API Reference
          • Create Credential Code
          • Create Credential Challenge
          • Create Credential Challenge With Code
          • Create Credential
          • Create Credential With Code
          • Deactivate Credential
          • Activate Credential
          • List Credentials
      • Recovery
        • Send Recovery Code Email
        • Create Recovery Challenge
        • Create Delegated Recovery Challenge
        • Recover User
    • Wallets
      • Create Wallet
      • Update Wallet
      • Delete Wallet
      • [deprecated] Delegate Wallet
      • Get Wallet by ID
      • List Wallets
      • Get Wallet Assets
      • Get Wallet NFTs
      • Get Wallet History
      • Tag Wallet
      • Untag Wallet
      • Transfer Asset
      • Get Transfer Request by ID
      • List Transfer Requests
      • Sign and Broadcast Transaction
        • Algorand
        • Aptos
        • Bitcoin / Litecoin
        • Canton
        • Cardano
        • EVM
        • Solana
        • Stellar
        • Tezos
        • TRON
        • XRP Ledger (Ripple)
      • Get Transaction Request by ID
      • List Transaction Requests
      • [deprecated] Generate Signature
      • Advanced Wallet APIs
        • Import Wallet
        • [deprecated] Export Wallet
    • Fee Sponsors
      • Create Fee Sponsor
      • Get Fee Sponsor
      • List Fee Sponsors
      • Activate Fee Sponsor
      • Deactivate Fee Sponsor
      • Delete Fee Sponsor
      • List Sponsored Fees
    • Keys
      • Create Key
      • Update Key
      • Delete Key
      • Delegate Key
      • Get Key by ID
      • List Keys
      • Generate Signature
        • Algorand
        • Aptos
        • Bitcoin / Litecoin
        • Cardano
        • Cosmos Appchain
        • EVM
        • Solana
        • Stellar
        • Substrate (Polkadot)
        • Tezos
        • TON
        • TRON
        • XRP Ledger (Ripple)
      • Get Signature Request by ID
      • List Signature Requests
      • Advanced Key APIs
        • Import Key
        • Export Key
        • Deterministic Derivation
    • Networks
      • Estimate fees
      • Read Contract
      • Validators
        • Create Validator
        • List Validators
    • Policy Engine
      • Policies Overview
      • API Reference
        • Create Policy
        • Get Policy
        • List Policies
        • Update Policy
        • Archive Policy
        • Get Approval
        • List Approvals
        • Create Approval Decision
    • Permissions
      • Permissions Overview
      • API Reference
        • Get Permission
        • List Permissions
        • Create Permission
        • Update Permission
        • Archive Permission
        • Assign Permission
        • Revoke Permission
        • List Permission Assignments
    • Webhooks
      • Create Webhook
      • Get Webhook
      • List Webhooks
      • Update Webhook
      • Delete Webhook
      • Ping Webhook
      • Get Webhook Event
      • List Webhook Events
    • Dfns Change Log
    • API Errors
  • Integrations
    • Exchanges
      • Kraken
      • Binance
      • Coinbase Prime
      • API Reference
        • Create Exchange
        • List Exchanges
        • Get Exchange
        • Delete Exchange
        • List Exchange Accounts
        • List Exchange Account Assets
        • Create Exchange Deposit
        • Create Exchange Withdrawal
    • AML / KYT
      • Chainalysis
    • Staking
      • API Reference
        • Create Stake
        • Create Stake Action
        • List Stakes
        • List Stake Actions
        • get Rewards
    • Fiat On/Off-Ramps
    • Account Abstraction on EVMs
  • Advanced Topics
    • Authentication
      • API Authentication
      • Request Headers
      • Credentials
        • Generate a Key Pair
        • User Credentials
        • Access Token Credentials
        • Storing WebAuthn Credentials in Password Managers
      • Request Signing
      • API objects
    • Delegated Signing
    • API Idempotency
    • FAQ
  • Guides
    • Passkey Settings - Migration guide
    • Keys & Multichain - Migration Guide
Powered by GitBook
On this page
  • Webhook Events
  • Supported Webhook Events
  • Webhook Event Data
  • Webhook Event Ordering
  • Webhook Event Deliveries & Retries
  • Webhooks best practices
  • Local Development
  1. API Docs

Webhooks

Listen for events so you can automatically trigger reactions.

Last updated 2 months ago

When integrating with Dfns, you might want your applications to receive events as they occur in your Dfns account, so that your backend systems can execute actions accordingly.

To start being notified about Webhook Events, you first need to register webhooks. After you register them, Dfns can push real-time event data to your application’s webhook endpoint when events happen in your Dfns account. Dfns uses a POST http/https request to send webhook events to your app, as a JSON payload which includes a .

Receiving webhook events are particularly useful for listening to asynchronous events, such as doing a wallet transfer request.

Webhook Events

When an event occurs in the system which a Webhook is subscribed to, we create a Webhook Event object containing data around the event that happened. We then:

  1. Send the event to your Webhook endpoint

  2. Capture a trace of the event so you can later check all Webhook Events sent to your webhooks (through or endpoints)

We only keep a trace of Webhook Events in our system for a retention period of 31 days. Past that, they are discarded, so you cannot see them using or endpoints.

Here's an example of a Webhook Event of kind "wallet.transfer.requested" delivered to your webhook:

{
  "id": "wh-xxx-xxxxxxx",
  "kind": "wallet.transfer.requested",
  "date": "2023-12-04T10:02:22.280Z",
  "data": {
    "transferRequest": {
      "id": "xfr-1vs8g-c1ub1-xxxxxxxxxxxxxxxx",
      "walletId": "wa-39abb-e9kpk-xxxxxxxxxxxxxxxx",
      "network": "EthereumSepolia",
      "requester": {
        "userId": "us-3v1ag-v6b36-xxxxxxxxxxxxxxxx",
        "tokenId": "to-7mkkj-c831n-xxxxxxxxxxxxxxxx",
        "appId": "ap-24vva-92s32-xxxxxxxxxxxxxxxx"
       },
      "requestBody": {
        "kind": "Native",
        "to": "0xb282dc7cde21717f18337a596e91ded00b79b25f",
        "amount": "1000000000"
      },
      "dateRequested": "2023-05-08T19:14:25.568Z",
      "status": "Pending"
    }
  },
  "status": "200",
  "timestampSent": 1701684144,
}

Supported Webhook Events

Event Enum
Description

wallet.blockchainevent.detected

wallet.created

wallet.exported

wallet.delegated

wallet.signature.requested

wallet.signature.failed

wallet.signature.rejected

wallet.signature.signed

wallet.transaction.requested

wallet.transaction.failed

wallet.transaction.rejected

wallet.transaction.broadcasted

wallet.transaction.confirmed

wallet.transfer.requested

wallet.transfer.failed

wallet.transfer.rejected

wallet.transfer.broadcasted

wallet.transfer.confirmed

policy.triggered

A policy got triggered upon some activity (the policy rule got evaluated, and it triggered)

policy.approval.pending

policy.approval.resolved

Webhook Event Data

Each webhook event has a "data" property, which shape depends on its kind. Here's an overview of the shape of the data for each kind:

data
{ // Wallet object, as in "Create Wallet" endpoint response
  "wallet": {      
    "id": "wa-xxx-xxxxxxxxx",
    ...
  }
}
data:
{ // Wallet Transfer Request object as in "Wallet Send Transfer" endpoint
  "transferRequest": {
    "id": "xfr-xxx-xxxxxxxxx",
    "walletId": "wa-xxx-xxxxxxxx",
    ...
  }
}
data
{ // Wallet Transaction Request as in "Wallet Broadcast Transaction" endpoint
  "transactionRequest": {
    "id": "tx-xxx-xxxxxxxxx",
    "walletId": "wa-xxx-xxxxxxxx",
    ...
  }
}
data
{ // Wallet Signature Request object as in "Wallet Generate Signature" endpoint
  "signatureRequest": {
    "id": "sig-xxx-xxxxxxxxx",
    "walletId": "wa-xxx-xxxxxxxx",
    ...
  }
}

  • For wallet.blockchainevent.detected various event kinds are available depending on the indexed chain:

Kind
Chains

NativeTransfer

All

Aip21Transfer

Aptos

AsaTransfer

Algorand

CoinTransfer, LockedCoinTransfer

Iota

Erc20Transfer, Erc721Transfer

Evm

Tep74Transfer

Ton

Trc10Transfer, Trc20Transfer, Trc721Transfer

Tron

Sep41Transfer

Stellar

SplTransfer, Spl2022Transfer

Solana

UtxoTransfer

Bitcoin, Litecoin, Dogecoin, Kaspa

data
{ // Blockchain Event object as in "Wallet Get Wallet History" endpoint
  "blockchainEvent": {
    "kind": "Erc20Transfer",
    "contract": "0x......",
    "from": "0x......",
    "to": "0x......",
    "direction": "In",
    ...
  }
}
  • For policy.triggered

data
{
  "policyEvaluation": {
    "id": "plce-xxxxxxx",
    "triggered": true,
    "reason": "Transfer amount (USD 10000) is above limit (USD 5000). ",
    "date": "2024-06-28T09:09:54.437Z",

    "policy": { // policy object as in "Get Policy" endpoint
      "id": "plc-xxxxxxx",
      "name": "Accounting wallet transfer limit",
      ...,
    },

    "activity": {
      "kind": "Wallets:Sign"
      "transferRequest": { // transfer request object as in "Get Transfer" endpoint
        "id": "xfr-xxxxxxx",
        ...
      }
    },

    "context": { // its content depends on the kind of the policy rule
      "transactionAmount": {
        "value": "10000,
        "currency": "USD",
      },
    },
  }
}
data
{ // Approval object
  "approval": {      
    "activityId": "cr-2100g-xxxxxxxxx",
    ...
  }
}

Webhook Event Ordering

Your endpoint shouldn’t expect delivery of these events in this order, and needs to handle delivery accordingly.

Webhook Event Deliveries & Retries

During an webhook event delivery attempt, if we cannot reach your webhook endpoint, or if your endpoint returns anything else than a 200 status code in the response, we consider the delivery of this event has failed.

In such a case, Dfns is going to retry delivering it to your webhook, up to 5 total attempts over 24 hours, with an exponential backoff (delays from first attempt: 1m, 12min, 2h, 1d).

The event that your webhook handler will receive (in your server), will include the attempt number in the payload (deliveryAttempt: 1 for the first attempt). Also, if it includes the field retryOf: "whe-xxxxxxx" , it indicates that this event you are receiving, is a "retry of" a previous Webhook Event which failed delivering.

If your webhook has been disabled or deleted when Dfns attempts a retry, future retries of that event are prevented. However, if you disable and then re-enable a webhook endpoint before Dfns can retry, you can still expect to see future retry attempts.

Webhooks best practices

Review these best practices to make sure your Webhooks remain secure and work properly.

Respond quickly with a 200

The webhook event handler defined on your server, when it receives a new event from Dfns, should be quick to handle it, and return quickly a 200 status code to indicate that the event was indeed delivered. If your handler expects to do some processing taking longer than a few seconds, you should consider adding this to a queue for processing on your side. Otherwise the request delivering the event might timeout.

Also, your handler should catch any error that happens on your side, so it still respond with a 200 response to Dfns, indicating that you received it already, otherwise the same event might be retried a few times and fail all the same.

Verify events are sent from Dfns

Verify webhook signatures to confirm that received events are sent from Dfns. Dfns signs webhook events it sends to your endpoints by including a signature in each event’s X-DFNS-WEBHOOK-SIGNATURE header. This allows you to verify that the events were sent by Dfns, not by a third party.

X-DFNS-WEBHOOK-SIGNATURE: sha256=33008aa9673b764cc752362034dfe49ef466315c45d62b3e8cb8588b23d0d06a

Here is an example function showing how you can validate the webhook event signature:

const crypto = require('crypto')

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET // the webhook secret you got upon webhook creation
const REPLAY_ATTACK_TOLERANCE = 5 * 60 // 5 minutes

function verifyDfnsWebhookSignature(eventPayload, eventSignature) {
  const messageToSign = JSON.stringify(eventPayload) // this assumes "eventPayload" was already JSON-parsed, and is an object (the full payload of the webhook event)

  const signature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(messageToSign)
    .digest('hex')

  const trustedSig = Buffer.from(`sha256=${signature}`, 'ascii')
  const untrustedSig = Buffer.from(eventSignature, 'ascii')

  const isSignatureValid = crypto.timingSafeEqual(trustedSig, untrustedSig) // using a constant-time equality comparison (to avoid timing attacks)

  const now = new Date().getTime() / 1000 // your server unix timestamp
  const isTimestampWithinTolerance = Math.abs(now - eventPayload.timestampSent) < REPLAY_ATTACK_TOLERANCE

  return isSignatureValid && isTimestampWithinTolerance
}

Only listen to event types your integration requires

Configure your webhook endpoints to receive only the types of events required by your integration. Listening for extra events (or all events) puts undue strain on the server and we don’t recommend it.

Handle duplicate events

Webhook endpoints might occasionally receive the same event more than once

We don't guarantee webhook event uniqueness. You should guard against duplicate event by making sure your your event processing is idempotent.

Receive events with an HTTPS server

If you use an HTTPS URL for your webhook, we validate that the connection to your server is secure before sending your webhook data. For this to work, your server must be correctly configured to support HTTPS with a valid server certificate.

Exempt webhook route from CSRF protection

class DfnsController < ApplicationController
  # If your controller accepts requests other than webhooks,
  # you'll probably want to use `protect_from_forgery` to add CSRF
  # protection for your application. But don't forget to exempt
  # your webhook route!
  protect_from_forgery except: :webhook

  def webhook
    # Process webhook data in `params`
  end
end
import json

# Webhooks are always sent as HTTP POST requests, so ensure
# that only POST requests reach your webhook view by
# decorating `webhook()` with `require_POST`.
#
# To ensure that the webhook view can receive webhooks,
# also decorate `webhook()` with `csrf_exempt`.
@require_POST
@csrf_exempt
def webhook(request):
  # Process webhook data in `request.body`

Local Development

To add a new webhook, you first need a http server which can receive requests from public internet.

As an example, here are steps to create a basic Express server in NodeJS:

  • Create a new npm project, install express

mkdir basic-server && cd basic-server
npm init
npm install express
  • Add a new file index.js :

const express = require('express')

const app = express()
const port = 3000

app.use(express.json())

app.post('/', (req) => {
  console.log('Received event:', req.body)
})

app.listen(port, () => {
  console.log(`Listening on port ${port}`)
})
  • In one terminal, run your server

node index.js
ngrok http 3000

If you use a free Ngrok account, every time you re-launch the tunnel you'll get a new url, so make sure you update the Dfns webhook url to test.

The list below shows which event kinds webhooks can subscribe to ⬇️ (see section for details on each event kind)

A wallet event has been detected on chain (eg. a deposit). Note: This is only available for .

A wallet has been .

A wallet has been .

A wallet has been .

A request has been created.

A request has failed to process.

A request with a policy approval has been rejected.

A request has completed.

A request has been created.

A request has failed to process.

A request with a policy approval has been rejected.

A request has been submitted to the mempool.

A request has been confirmed on chain. Note: This is only available for .

A request has been created.

A request has failed to process.

A request with a policy approval has been rejected.

A request has been submitted to the mempool.

A request has been confirmed on chain. Note: This is only available for .

A new process has been created and is pending.

A new process is finalized: it's either approved or rejected.

For wallet.created, wallet.exported, wallet.delegated see the :

For wallet.transfer.requested, wallet.transfer.failed, wallet.transfer.rejected, wallet.transfer.broadcasted, wallet.transfer.confirmed see the :

For wallet.transaction.requested, wallet.transaction.failed, wallet.transaction.rejected, wallet.transaction.broadcasted, wallet.transaction.confirmed see the :

For wallet.signature.requested, wallet.signature.failed, wallet.signature.rejected, wallet.signature.signed see the :

for example, see the :

For policy.approval.pending and policy.approval.resolved, see the :

Dfns doesn’t guarantee delivery of events in the order in which they’re generated. For example, when a wallet is picked up on-chain by our blockchain indexers, we might generate the following events:

wallet.transfer.confirmed - this event notifies you that the that you made has been confirmed on chain

wallet.blockchainevent.detected- this event notifies you of the new Blockchain Event detected and added to your blockchain events

Every event delivery attempt will create a new Webhook Event, with its own unique ID, containing the same data than the previous event which failed delivering. So in the endpoint, every Webhook Event you will see is a unique delivery attempt (potentially of the same original event).

Additionally, if you fetch Webhook Events we tried delivering, using the or , you will be able to see the deliveryFailed boolean field indicating if delivery succeeded or not, as well as the nextAttemptDate: "2024-01-24-xxxxxx" date showing you around which time the next delivery attempt to your webhook will occur (if delivery failed).

If you want to fetch all Webhook Events which failed delivering to your webhook, you can use the with the query parameter deliveryFailed=true. And amongst all those returned, you can see those which failed delivering, and will not retry in future (because reached maximum retry attempt), by filtering those which have no nextAttemptDate .

Dfns signatures is a of the received event payload, using hash function and the webhook secret as the secret. It has this shape:

If you’re using Rails, Django, or another web framework, your site might automatically check that every POST request contains a CSRF token. This is an important security feature that helps protect you and your users from attempts. However, this security measure might also prevent your site from processing legitimate events. If so, you might need to exempt the webhooks route from CSRF protection.

During development, one way to achieve this (without deploying a new server on a cloud provider), is to spin up a local server on your machine (localhost), and then use a tunnel services (eg ) to create a public url pointing to your local server.

In another terminal, start a tunnel pointing to your local server

Now, you can using the url displayed in the result of above the terminal (looking like "https://xxxxxx.ngrok-free.dev"), and test that the webhook is setup properly by using . If properly setup, you should see incoming events received by your local server.

Get Wallet response
Get Transfer response
Get Transaction response
Get Signature response
Get History response
List Approvals response
Transfer
Transfer Request
Wallet History
List Webhook Events
List Webhook Events
Get Webhook
List Webhook Events
HMAC
SHA-256
cross-site request forgery
https://ngrok.com
ngrok
Create a Webhook
Ping Webhook
Webhook Event Data
Tier-1 chains
created
exported
delegated
generate signature
generate signature
generate signature
generate signature
broadcast transaction
broadcast transaction
broadcast transaction
broadcast transaction
broadcast transaction
Tier-1 chains
wallet transfer
wallet transfer
wallet transfer
wallet transfer
wallet transfer
Tier-1 chains
List Webhook Events
Get Webhook Event
List Webhook Events
Get Webhook Event
Webhook Event Object
Approval
Approval