Agent skill

Dropbox Webhooks Skill

Receive and verify Dropbox API webhooks. Use when setting up Dropbox webhook handlers, answering the Dropbox challenge verification request, debugging X-Dropbox-Signature verification, or handling list_folder change notifications and fetching changes with /files/list_folder/continue.

Install this skill

npx skills add hookdeck/webhook-skills --skill dropbox-webhooks


These are webhooks for Dropbox API apps (file and folder changes), not Dropbox Sign.

When to Use This Skill

  • How do I receive Dropbox webhooks?
  • How do I answer the Dropbox webhook challenge verification request?
  • How do I verify the X-Dropbox-Signature header?
  • How do I find out what changed after a Dropbox list_folder notification?
  • Why is my Dropbox webhook URI failing verification or signature checks?

How Dropbox Webhooks Work

Dropbox webhooks are change pings, not event payloads. A notification lists the account IDs that have changes; you then call the API (/files/list_folder/continue with your saved cursor) to get the actual file changes. There is no event-type field or header.

  1. Verification request — when you add the URI in the App Console (and periodically after), Dropbox sends GET <uri>?challenge=<random>. Reply 200 echoing the value with Content-Type: text/plain and X-Content-Type-Options: nosniff.
  2. Notification — POST with JSON body and an X-Dropbox-Signature header.

Verification (core)

X-Dropbox-Signature is the hex HMAC-SHA256 of the raw request body, keyed with your app secret (the "App secret" in the App Console). No timestamp, no prefix. Compare timing-safe.

Node:

const crypto = require('crypto');

function verify(rawBody, signature, appSecret) {
  if (!signature) return false;
  const expected = crypto.createHmac('sha256', appSecret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false; // different lengths
  }
}

// GET challenge: res.set({ 'Content-Type': 'text/plain', 'X-Content-Type-Options': 'nosniff' }).send(req.query.challenge)

Python:

import hmac, hashlib

def verify(raw_body: bytes, signature: str, app_secret: str) -> bool:
    if not signature:
        return False
    expected = hmac.new(app_secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)

For complete handlers with route wiring, challenge handling, and tests, see:

Notification Payload

Dropbox has no named event types. The body contains these top-level keys:

KeyDescription
list_folder.accountsAccount IDs with file changes. Call /files/list_folder/continue with the stored cursor for each
delta.usersLegacy user IDs (deprecated /delta API)
{"list_folder": {"accounts": ["dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"]}, "delta": {"users": [12345678]}}

Respond 200 quickly (Dropbox expects a response within 10 seconds) and do the list_folder/continue work asynchronously.

Important Headers

HeaderDescription
X-Dropbox-SignatureHex HMAC-SHA256 of the raw body, keyed with the app secret

Environment Variables

DROPBOX_APP_SECRET=your_app_secret   # App Console > Settings > App secret

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 dropbox --path /webhooks/dropbox

Use the Hookdeck URL as the webhook URI in the App Console; Dropbox's challenge request is forwarded to your local server.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →