Webhooks

When integrating with IBM Digital Asset Haven, you may want your applications to receive events as they occur in your IBM Digital Asset Haven account so that your backend systems can execute actions accordingly. To start receiving webhook events, you must first register webhooks. After registration, IBM Digital Asset Haven pushes real-time event data to your application’s webhook endpoint when events occur in your account. IBM Digital Asset Haven uses an HTTP POST request to send webhook events as JSON payloads, which include a Webhook Event Object. Webhook events are particularly useful for listening to asynchronous actions, such as wallet transfer requests. For more details about webhook configuration, see API Reference.

Webhooks best practices

Follow these best practices to keep your webhooks secure and reliable.

Respond quickly with a 200

Your webhook handler should respond with a 200 status code immediately after receiving an event. If processing takes longer than a few seconds, enqueue the task for asynchronous handling. Otherwise, the request may time out.

Always catch errors in your handler and still return 200 to IBM Digital Asset Haven. If you fail to acknowledge receipt, IBM Digital Asset Haven may retry the same event multiple times.

Verify events are sent from IBM Digital Asset Haven

Verify webhook signatures to confirm that events originate from IBM Digital Asset Haven. Each event includes a signature in the X-DFNS-WEBHOOK-SIGNATURE header.

IBM Digital Asset Haven signs events using an HMAC with the SHA-256 hash function and your webhook secret.

Example header:
X-DFNS-WEBHOOK-SIGNATURE: sha256=33008aa9673b764cc752362034dfe49ef466315c45d62b3e8cb8588b23d0d06a
  • Node.js
    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
    }
  • Python
    import os
    import json
    import hmac
    import hashlib
    import time
    
    WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]  # the webhook secret you got upon webhook creation
    REPLAY_ATTACK_TOLERANCE = 5 * 60  # 5 minutes
    
    def verify_dfns_webhook_signature(event_payload: dict, event_signature: str) -> bool:
        # event_payload should be the parsed JSON object
        message_to_sign = json.dumps(event_payload, separators=(',', ':'))
    
        expected_signature = "sha256=" + hmac.new(
            WEBHOOK_SECRET.encode(),
            message_to_sign.encode(),
            hashlib.sha256
        ).hexdigest()
    
        # Use constant-time comparison to avoid timing attacks
        is_signature_valid = hmac.compare_digest(expected_signature, event_signature)
    
        now = time.time()
        is_timestamp_valid = abs(now - event_payload["timestampSent"]) < REPLAY_ATTACK_TOLERANCE
    
        return is_signature_valid and is_timestamp_valid
Listen only to required event types
Configure your webhook to receive only the events needed for your integration. Listening to unnecessary events increases server load and is not recommended.
Handle duplicate events
Event uniqueness is not guaranteed. Ensure your event processing is idempotent to avoid duplicate handling.
Note: Webhook endpoints may occasionally receive the same event more than once.
Use HTTPS
Webhook endpoints must use HTTPS with a valid server certificate. IBM Digital Asset Haven validates the connection before sending data.
Exempt webhook route from CSRF protection
Some frameworks (such as Rails and Django) enforce CSRF protection on all POST requests. This may block legitimate webhook events. Exempt your webhook route from CSRF checks.
  • Rails
    class DfnsController < ApplicationController
      protect_from_forgery except: :webhook
    
      def webhook
        # Process webhook data in `params`
      end
    end
  • Django
    import json
    
    @require_POST
    @csrf_exempt
    def webhook(request):
      # Process webhook data in `request.body`

Local development

To add a webhook, you need an HTTP server accessible from the public internet. During development, you can run a local server and expose it using a tunneling service such as Ngrok.

Example: Basic Express server in Node.js
  1. Create a new npm project, install Express.
    mkdir basic-server && cd basic-server
    npm init
    npm install express
  2. 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}`)
    })
  3. In a terminal, run your server.
    node index.js
  4. In another terminal, start an ngrok tunnel pointing to your local server.
    ngrok http 3000
  5. Use the generated Ngrok URL (for example, https://xxxxxx.ngrok-free.dev) to Create a Webhook.
  6. Test the webhook with Ping Webhook. If configured correctly, you will see events received by your local server.
Note: Free Ngrok accounts generate a new URL each time you restart the tunnel. Update your IBM Digital Asset Haven webhook URL accordingly.