Depa LogoDocsv1
Webhooks

Webhook Verification

Cryptographic HMAC-SHA256 signature verification for real-time asynchronous event notifications.

Depa delivers real-time notifications for payment confirmations, KYC status updates, and ledger events using HTTPS POST webhooks.

To protect against man-in-the-middle attacks and forgery, all incoming webhook requests contain a Depasify-Signature HTTP header. Your server must verify this signature before processing the payload.


The Signature Header Format

The Depasify-Signature header consists of two comma-separated key-value pairs:

Depasify-Signature: t=1714562415,v1=6d4e28e469c5e3d740c83a54b3879fcfd2b8344697924d5e89d81d26ad5df19f
  • t: The Unix timestamp when the webhook was dispatched.
  • v1: The HMAC-SHA256 hex digest computed over ${t}.${raw_request_body} using your Webhook Secret.

Verification Code Samples

Node.js / TypeScript

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhookSignature(
  rawBody: string,
  signatureHeader: string,
  secret: string
): boolean {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => {
      const [key, val] = part.split("=");
      return [key.trim(), val.trim()];
    })
  );

  const timestamp = parts["t"];
  const receivedSignature = parts["v1"];

  if (!timestamp || !receivedSignature) return false;

  // Replay attack prevention (reject webhooks older than 5 minutes)
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - parseInt(timestamp, 10)) > 300) {
    return false;
  }

  const payloadToSign = `${timestamp}.${rawBody}`;
  const computedSignature = createHmac("sha256", secret)
    .update(payloadToSign)
    .digest("hex");

  return timingSafeEqual(
    Buffer.from(receivedSignature, "hex"),
    Buffer.from(computedSignature, "hex")
  );
}

Python

import hmac
import hashlib
import time

def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    pairs = dict(item.split("=") for item in signature_header.split(","))
    timestamp = pairs.get("t")
    received_sig = pairs.get("v1")

    if not timestamp or not received_sig:
        return False

    # Prevent replay attacks
    if abs(time.time() - int(timestamp)) > 300:
        return False

    payload = f"{timestamp}.".encode("utf-8") + raw_body
    computed_sig = hmac.new(
        secret.encode("utf-8"),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(received_sig, computed_sig)

Ruby

require 'openssl'

def verify_webhook(request_body, signature_header, secret)
  parts = signature_header.split(',').to_h { |part| part.split('=') }
  timestamp = parts['t']
  received_sig = parts['v1']

  return false unless timestamp && received_sig

  # Reject requests older than 5 minutes
  return false if (Time.now.to_i - timestamp.to_i).abs > 300

  data = "#{timestamp}.#{request_body}"
  computed_sig = OpenSSL::HMAC.hexdigest('SHA256', secret, data)

  Rack::Utils.secure_compare(received_sig, computed_sig)
end

On this page

🍪 We do not track your behaviour or use any cookie on this site.