> ## Documentation Index
> Fetch the complete documentation index at: https://docs.drime.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Get told when a connected account changes, instead of asking

If you have integrated Dropbox webhooks, this is the same design: a challenge when you set the endpoint, then signed `POST` requests that name the accounts that changed, and nothing else.

Webhooks are available to applications created as **A website or a server**. Drime has to reach a server of yours, and a desktop, mobile or script application has none.

## 1. Set the endpoint

In the [developer console](https://app.drime.cloud/developers), open your application, then **Webhooks**. Paste an `https://` address and click **Verify and save**.

Drime calls it straight away with a challenge:

```
GET https://your-app.com/drime/webhook?challenge=Xq4vB8sLm2NcT6yRw1Ke9JdFh3Pz7UaG
```

Answer `200` with the challenge as the whole body:

```
HTTP/1.1 200 OK
Content-Type: text/plain
X-Content-Type-Options: nosniff

Xq4vB8sLm2NcT6yRw1Ke9JdFh3Pz7UaG
```

You have 10 seconds, and redirects are not followed. Nothing is saved until the challenge comes back, and nothing is ever sent to an endpoint that has not answered one. That is what stops someone from typing your address into their own application.

The address must:

* start with `https://`;
* use a hostname, not an IP address;
* resolve to public addresses only, which Drime checks again before every delivery, not only when you save;
* carry no credentials and no `#fragment`;
* be 500 characters at most.

## 2. Copy the signing secret

Once the endpoint is saved, the **Webhooks** tab shows its **signing secret**, starting with `whsec_`. Keep it on your server, like a password: it is what proves a notification comes from Drime.

To get a new one, remove the endpoint and save it again. The old secret stops working at once.

## 3. Receive notifications

```
POST https://your-app.com/drime/webhook
Content-Type: application/json
User-Agent: Drime-Webhook/1.0
X-Drime-Signature: 285215da10359586d6d7380abad6e81e6979a32da3f54b0a37a1910c7c789826

{"list_folder":{"accounts":["1042","2099"]}}
```

The body names **who** changed, never **what**. Each entry is a Drime account id, as a string: the `user.id` that [`GET /users/me`](/api-reference/user/get-logged-user) returned when that user authorized you. For each account, ask the API what is new, with the token you hold for that user.

Answer `200` within **10 seconds**, then do the work in a queue. Drime does not wait and does not resend: whatever you miss will show up the next time you look, because a notification never carries the data.

## 4. Check the signature

`X-Drime-Signature` is the hexadecimal HMAC-SHA256 of the **raw** request body, keyed with your signing secret. Compute it on the exact bytes you received, before parsing anything, and compare in constant time. Your endpoint is public: anyone can send it a request.

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  const crypto = require('crypto');
  const express = require('express');
  const app = express();

  app.get('/drime/webhook', (req, res) => {
    res.set('Content-Type', 'text/plain');
    res.set('X-Content-Type-Options', 'nosniff');
    res.send(String(req.query.challenge || ''));
  });

  // express.raw, not express.json: the signature covers the raw bytes.
  app.post('/drime/webhook', express.raw({ type: '*/*' }), (req, res) => {
    const expected = Buffer.from(
      crypto
        .createHmac('sha256', process.env.DRIME_WEBHOOK_SECRET)
        .update(req.body)
        .digest('hex'),
    );
    const given = Buffer.from(req.get('X-Drime-Signature') || '');

    if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) {
      return res.sendStatus(403);
    }

    res.sendStatus(200);

    const { accounts } = JSON.parse(req.body).list_folder;
    queue.push(accounts); // your own job queue
  });
  ```

  ```php PHP theme={null}
  <?php

  if ($_SERVER['REQUEST_METHOD'] === 'GET') {
      header('Content-Type: text/plain');
      header('X-Content-Type-Options: nosniff');
      echo $_GET['challenge'] ?? '';
      exit;
  }

  $raw = file_get_contents('php://input');
  $expected = hash_hmac('sha256', $raw, getenv('DRIME_WEBHOOK_SECRET'));

  if (!hash_equals($expected, $_SERVER['HTTP_X_DRIME_SIGNATURE'] ?? '')) {
      http_response_code(403);
      exit;
  }

  http_response_code(200);

  $accounts = json_decode($raw, true)['list_folder']['accounts'];
  // Queue the work for each account.
  ```

  ```python Python (Flask) theme={null}
  import hashlib
  import hmac
  import os

  from flask import Flask, abort, request

  app = Flask(__name__)
  SECRET = os.environ['DRIME_WEBHOOK_SECRET'].encode()


  @app.get('/drime/webhook')
  def challenge():
      return request.args.get('challenge', ''), 200, {
          'Content-Type': 'text/plain',
          'X-Content-Type-Options': 'nosniff',
      }


  @app.post('/drime/webhook')
  def notification():
      raw = request.get_data()
      expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
      if not hmac.compare_digest(expected, request.headers.get('X-Drime-Signature', '')):
          abort(403)

      accounts = request.get_json(force=True)['list_folder']['accounts']
      queue_work(accounts)  # your own job queue
      return '', 200
  ```
</CodeGroup>

<Tip>
  To test your code: with the secret `whsec_example`, the body `{"list_folder":{"accounts":["1042","2099"]}}` signs to `285215da10359586d6d7380abad6e81e6979a32da3f54b0a37a1910c7c789826`.
</Tip>

## What sends a notification

* Something is **created** in the account: an upload, a new folder or document, a copy.
* Something is **deleted**: moved to the trash, or deleted for good.

Renames, moves, edits and restores do not send one. Neither does anything in the Vault, which is end-to-end encrypted.

A change notifies every application that the **owner** of the changed item has connected, as long as its endpoint works. The notification names the account, not a folder or a workspace: an application that works in [its own folder](/oauth/app-folder) can be told about a change made elsewhere in the Drive. List your folder to see whether anything changed in it.

## Frequency and limits

| | |
| - | - |
| Frequency | At most one round of notifications a minute per application, however much changed. A user dropping four hundred photos produces one notification. |
| Batching | One request names up to 500 accounts. More accounts in the same minute means more requests, 500 at a time. |
| Timeout | 10 seconds. |
| Retries | None. If a request fails, the rest of that minute's requests to your endpoint are dropped too. |
| Redirects | Not followed. Give the final address. |
| Failures | After 20 failures in a row the endpoint is switched off, and the **Webhooks** tab says so. Save it again to switch it back on. |

Drime only notifies an application about accounts that have connected it, and stops as soon as the user disconnects it.

## Checklist

* The challenge handler answers with the challenge only, as `text/plain`.
* The signature is checked on the raw body, in constant time, with the signing secret.
* `200` goes out before the work starts.
* The work can run twice for the same account without harm, and does not depend on every notification arriving.
* The endpoint resolves to a public address and is not behind a redirect.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.