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
challengeverification request? - How do I verify the
X-Dropbox-Signatureheader? - How do I find out what changed after a Dropbox
list_foldernotification? - 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_ with your saved cursor) to get the actual file changes. There is no event-type field or header.
- Verification request — when you add the URI in the App Console (and periodically after), Dropbox sends
GET <uri>?challenge=<random>. Reply200echoing the value withContent-Type: text/plainandX-Content-Type-Options: nosniff. - Notification —
POSTwith JSON body and anX-Dropbox-Signatureheader.
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:
| Key | Description |
|---|---|
list_folder.accounts | Account IDs with file changes. Call /files/list_ with the stored cursor for each |
delta.users | Legacy 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
| Header | Description |
|---|---|
X-Dropbox-Signature | Hex 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
- references/overview.md - Dropbox webhook concepts and payload
- references/setup.md - App Console configuration
- references/verification.md - Challenge and signature verification details