Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dragdropdo.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are the recommended way to receive asynchronous notifications about Business API operations. They are driven by the task system and delivered by the WebhookService with retry and detailed audit logging.

Webhook lifecycle

The webhook flow is:
  1. Trigger event – a file task or main task transitions state.
  2. Prepare payload – the system builds a JSON payload and HMAC signature headers.
  3. Send to client – an HTTP request is made to your registered webhook_url.
  4. Log to audit table – each attempt is recorded with status, payload, response, and error.
  5. Retry (optional) – failed deliveries are retried until the configured limit is reached.
All deliveries are logged in the WEBHOOK_AUDIT_LOGS table, including status, attempt number, response status, and any error message.

Registering webhooks

Webhooks are registered and managed through the D3 dashboard:
  1. Sign in to your account at dragdropdo.com/auth/signin
  2. Navigate to your Account section
  3. Go to the Register Webhook section
  4. Enter your webhook URL and optional webhook secret
  5. Save your webhook configuration
You can also configure webhooks when generating an API key in the dashboard by providing a webhook URL during the API key creation process.

Events

From the Business API design, the main events are:
  • operation.completed – main task finished (all file tasks completed or partially completed).
  • operation.failed – main task failed (all file tasks failed).
  • fileTask.completed – individual file task completed.
  • fileTask.failed – individual file task failed.

operation.completed / partially completed

{
  "type": "operation.completed",
  "timestamp": "2022-11-03T20:26:10.344522Z",
  "payload": {
    "main_task": {
      "id": "aaa...",
      "status": "completed",
      "count": {
        "total": 2,
        "success": 2,
        "failed": 0
      },
      "file_data": [
        {
          "file_key": "bbb...",
          "status": "completed",
          "download_link": "xz..."
        },
        {
          "file_key": "ccc...",
          "status": "failed",
          "error": "Internal Server error/invalid input file"
        }
      ]
    }
  }
}

operation.failed

{
  "type": "operation.failed",
  "timestamp": "2022-11-03T20:26:10.344522Z",
  "payload": {
    "main_task": {
      "id": "aaa...",
      "status": "failed",
      "count": {
        "total": 2,
        "success": 0,
        "failed": 2
      }
    }
  }
}

fileTask.completed

{
  "type": "fileTask.completed",
  "timestamp": "2022-11-03T20:26:10.344522Z",
  "data": {
    "file_key": "aaa...",
    "status": "completed"
  }
}

fileTask.failed

{
  "type": "fileTask.failed",
  "timestamp": "2022-11-03T20:26:10.344522Z",
  "data": {
    "file_key": "aaa...",
    "status": "failed",
    "error": "..."
  }
}

Receiving webhooks

Your webhook endpoint must accept POST requests with a JSON body. D3 sends the event payload along with an X-Webhook-Signature header (when a webhook_secret is configured). Your server should:
  1. Read the raw request body.
  2. Verify the HMAC-SHA256 signature.
  3. Parse the JSON payload and handle the event.
  4. Return a 2xx status code to acknowledge receipt.

Webhook handler example

import express from "express";
import crypto from "crypto";

const app = express();

app.post(
  "/webhooks/d3",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["X-Webhook-Signature"] as string;
    const body = req.body;

    if (!verifySignature(body, signature, process.env.D3_WEBHOOK_SECRET!)) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(body.toString());

    switch (event.type) {
      case "operation.completed":
        handleOperationCompleted(event.payload);
        break;
      case "operation.failed":
        handleOperationFailed(event.payload);
        break;
      case "fileTask.completed":
        handleFileTaskCompleted(event.data);
        break;
      case "fileTask.failed":
        handleFileTaskFailed(event.data);
        break;
      default:
        console.log("Unhandled event type:", event.type);
    }

    res.status(200).json({ received: true });
  }
);

function verifySignature(
  body: Buffer,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

function handleOperationCompleted(payload: any) {
  const { main_task } = payload;
  console.log(`Operation ${main_task.id} completed`);
  console.log(`  ${main_task.count.success}/${main_task.count.total} files succeeded`);
  for (const file of main_task.file_data) {
    if (file.status === "completed") {
      console.log(`  Download: ${file.download_link}`);
    }
  }
}

function handleOperationFailed(payload: any) {
  console.error(`Operation ${payload.main_task.id} failed`);
}

function handleFileTaskCompleted(data: any) {
  console.log(`File ${data.file_key} completed`);
}

function handleFileTaskFailed(data: any) {
  console.error(`File ${data.file_key} failed: ${data.error}`);
}

app.listen(3000, () => console.log("Webhook server listening on :3000"));

Verifying signatures

When a webhook_secret is configured, every webhook request includes an X-Webhook-Signature header containing the HMAC-SHA256 hex digest of the raw request body, keyed with your secret. Always verify signatures before processing events. Use a constant-time comparison function to prevent timing attacks.

Signature verification example

import crypto from "crypto";

function verifyWebhookSignature(
  rawBody: Buffer,
  signatureHeader: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Delivery & retries

The webhook audit and retry flow (simplified from the design docs):
  • On each attempt, a row is inserted into WEBHOOK_AUDIT_LOGS with:
    • event_type
    • target_url
    • payload
    • response_status
    • attempt_number
    • status (success / failed)
    • error_message
    • sent_at
  • If retries are enabled, failed attempts are rescheduled until either:
    • A retry succeeds.
    • The max retry count is reached.
You can inspect delivery health and failures through the webhook management section in your account dashboard.

Security

Recommended best practices:
  • Verify signatures – always validate the X-Webhook-Signature header using your webhook_secret and a constant-time comparison.
  • Use HTTPS – only register HTTPS endpoints to protect payloads in transit.
  • Respond quickly – return a 2xx within a few seconds; offload heavy processing to a background queue.
  • Idempotency – webhooks may be retried, so deduplicate using the event type + timestamp or track processed event IDs.
  • Treat as notifications – fetch sensitive data (like download URLs) via the Business API rather than relying solely on webhook payloads.