Dropbox Webhook: Example Payload, Headers and Signature

A Dropbox webhook tells you who has changes and nothing about what changed. Here is the notification, the challenge Dropbox sends before it will use your URL, and the API call that turns the ping into a list of files.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Dropbox, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Dropbox webhook?

A Dropbox webhook is an HTTP POST sent to a URI registered on an app in the App Console whenever files change in the Dropbox of any user who has linked that app. One URI serves the whole app: there are no per-user or per-folder subscriptions and no event types to choose.

The body is deliberately minimal. It lists the account ids that have changes, and that is all. No paths, no file names, no kind of change. The notification is a signal to call /files/list_folder/continue for each listed account with the cursor you saved last time; the response to that call is where the actual changes are. This keeps file names out of webhook traffic and means a missed notification loses nothing, because the next one leads to the same cursor.

What a Dropbox webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
X-Dropbox-Signature: 757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
Content-Type: application/json
User-Agent: DropboxWebhooks/1.0
{
  "list_folder": {
    "accounts": [
      "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
    ]
  },
  "delta": {
    "users": [
      12345678
    ]
  }
}

list_folder.accounts is the part to use: account ids in the dbid: form that the current API understands. delta.users repeats the same information as numeric ids for the retired first version of the API and can be ignored. One notification may name many accounts, and several changes made close together in one account are collapsed into a single notification.

Headers Dropbox sends

HeaderExampleWhat it is
X-Dropbox-Signature757107ea0eb2509fc211221cce984b8a37570b6d758…HMAC-SHA256 of the body, hex, with no prefix, keyed with the app secret.
Content-Typeapplication/jsonThe body is JSON.
User-AgentDropboxWebhooks/1.0Identifies the sender.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

How Dropbox signs a webhook

Dropbox computes an HMAC-SHA256 of the request body keyed with your app secret and sends the hex digest, with no prefix, in X-Dropbox-Signature. There is one secret for the whole app, the same one used in the OAuth flow, so it belongs on the server and nowhere else.

Nothing in the body is sensitive, which tempts people to skip the check. Do not: an unverified endpoint lets anyone make your server call the Dropbox API in a loop for any account id they can guess. Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function.

const crypto = require('crypto');

// rawBody: Buffer or string of the body exactly as received
function verifyDropbox(rawBody, header, appSecret) {
  const expected = crypto.createHmac('sha256', appSecret)
    .update(rawBody).digest('hex');
  const a = Buffer.from(header || '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

The challenge request, timeouts and disabling

When you add a webhook URI, Dropbox first sends it a GET request with a challenge query parameter. The endpoint must respond with that value as the body, and Dropbox asks for two response headers with it: Content-Type: text/plain and X-Content-Type-Options: nosniff, so the echoed value can never be interpreted as a web page. The webhook URLs created on this site answer exactly that way, which lets the URI be saved and real notifications inspected.

Notifications should be acknowledged within about ten seconds, per Dropbox's documentation at the time of writing, and because fetching the changes can take longer than that, the fetching belongs on a queue or a background thread. Failed deliveries are retried with a growing delay, and an endpoint that fails for long enough is disabled, with an email to the app's owner, until it is re-enabled in the App Console.

Catch a real Dropbox webhook

  1. Press Create webhook URL at the top of this page and copy the address.
  2. In the Dropbox App Console, open a test app (an app-folder app is the least intrusive) and scroll to Webhooks.
  3. Paste the address as the webhook URI and add it. The GET challenge appears in the inbox and the URI's status becomes enabled.
  4. Link a Dropbox account to the app, for instance by generating an access token for your own account on the same page.
  5. Add or change a file in that account's app folder and read the notification in the inbox above.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • You need a cursor before the webhook is useful. Call /files/list_folder once per user and store the cursor; the notification only tells you when to continue from it.
  • Two notifications can overlap. Process one account at a time, or two workers will fetch the same changes from the same cursor.
  • Team apps get a different list. Notifications for a Dropbox Business team app name teams and members instead of plain accounts.

api-tester.com is independent and is not affiliated with Dropbox. Providers change header names, limits and retry schedules; treat the official Dropbox documentation as the source of truth.

Related examples and tools

Other webhooks signed with an HMAC of the body

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Dropbox webhook FAQ

What does a Dropbox webhook payload look like?

A small JSON object with list_folder.accounts, an array of account ids that have file changes, and a legacy delta.users array. It does not say which files changed.

How do I find out what changed after a Dropbox webhook?

For each account id in the notification, call /files/list_folder/continue with the cursor you stored for that account. The response lists the changed entries and returns a new cursor to store.

How do I verify the X-Dropbox-Signature header?

Compute an HMAC-SHA256 of the raw request body using your app secret, hex-encode it and compare it with the header value using a constant-time comparison.

How do I answer the Dropbox webhook challenge?

Respond to the GET request with the value of the challenge query parameter as the body, with Content-Type: text/plain and X-Content-Type-Options: nosniff response headers.

Why was my Dropbox webhook disabled?

Dropbox disables a webhook URI after it fails repeatedly over an extended period. Fix the endpoint and re-enable the URI in the App Console.