# Box Webhooks

## When to Use This Skill

* How do I receive Box webhooks?
* How do I verify the `BOX-SIGNATURE-PRIMARY` header?
* How do I handle `FILE.UPLOADED`, `FOLDER.CREATED`, or `SIGN_REQUEST.COMPLETED` events?
* Why is my Box webhook signature verification failing?
* How do I dedupe Box webhook retries?

This skill covers **V2 webhooks** (created through the API per file or folder). V1 webhooks are unsigned and Box Sign's own webhooks are separate; neither is covered.

## Essential Code (USE THIS)

Box sends two signatures, `BOX-SIGNATURE-PRIMARY` and `BOX-SIGNATURE-SECONDARY`. Verify the primary one only:

* Signature: standard base64 (padded, 44 characters) HMAC-SHA256, no prefix
* Signed message: the **raw request body bytes, then the `BOX-DELIVERY-TIMESTAMP` header value**, with no separator
* Key: the app's **primary signature key**, used as a UTF-8 string (not decoded)

```javascript
const crypto = require('crypto');

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyBox(rawBody, signature, timestamp, secret) {
  if (!secret || !signature || !timestamp) return false;
  if (!/^[A-Za-z0-9+/]{43}=$/.test(signature)) return false; // validate before comparing
  const signed = Buffer.concat([rawBody, Buffer.from(timestamp, 'latin1')]); // body first, no separator
  const expected = crypto.createHmac('sha256', secret).update(signed).digest('base64');
  const a = Buffer.from(signature, 'latin1'), b = Buffer.from(expected, 'latin1');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// verifyBox(rawBody, req.headers['box-signature-primary'], req.headers['box-delivery-timestamp'], secret)

```

```python
import base64, hashlib, hmac, re

def verify_box(raw_body: bytes, signature, timestamp, secret: str) -> bool:
    if not secret or not signature or not timestamp or not re.fullmatch(r"[A-Za-z0-9+/]{43}=", signature):
        return False
    signed = b"".join([raw_body, timestamp.encode("latin-1")])  # body first, no separator
    digest = hmac.new(secret.encode(), signed, hashlib.sha256).digest()
    return hmac.compare_digest(signature.encode("ascii"), base64.b64encode(digest))

```

A missing or empty header, a malformed value, or a mismatch gets `401`.

> **Setup note (same text Hookdeck shows for its Box source):** Paste the primary signature key from the Box Developer Console; only the primary signature is verified, so during key rotation paste the key that signs it. Only V2 webhooks are signed.

**Timestamp window:** Box suggests rejecting a `BOX-DELIVERY-TIMESTAMP` (RFC 3339) older than 10 minutes. The examples leave that **off by default**, because Hookdeck retries and replays resend the original headers and timestamp, and Hookdeck itself applies no tolerance. Set `BOX_WEBHOOK_TOLERANCE_SECONDS` to a positive number (for example `600`) to opt in; unset or `0` is off. Box's own SDK helper (`WebhookValidator`) always enforces the 10 minutes and checks both keys, which is why the examples verify by hand. See [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/box-webhooks/references/verification.md).

**HTTP method:** Box's docs don't name the delivery method; they say only that "Box makes an HTTP call to the `address`". The examples accept GET, POST, PUT, PATCH and DELETE and verify the signature first for every method, treating a missing body as empty bytes. Hookdeck's Box source pre-selects POST without managing the method, so any other method the sender uses must also be allowed on the source.

**Dedupe on `id`:** the body's `id` is the same across Box's retries; `BOX-DELIVERY-ID` changes on every retry, so don't use it.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/box-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/box-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/box-webhooks/examples/fastapi/).

## Common Event Types

The body is `{type: "webhook_event", id, created_at, trigger, webhook, created_by, source, additional_info}`. Dispatch on `trigger`:

| Trigger | Description |
| --- | --- |
| `FILE.UPLOADED` | A file is uploaded or moved to the folder |
| `FILE.DOWNLOADED` | A file is downloaded |
| `FILE.TRASHED` | A file is moved to trash |
| `FILE.DELETED` | A file is permanently deleted |
| `FOLDER.CREATED` | A folder is created |
| `COMMENT.CREATED` | A comment is created |
| `COLLABORATION.CREATED` | A collaboration is created |
| `SHARED_LINK.CREATED` | A shared link is created |
| `METADATA_INSTANCE.UPDATED` | A metadata template instance is updated |
| `SIGN_REQUEST.COMPLETED` | A Box Sign request is completed |

Box documents 46 triggers in all, including file, folder, comment, collaboration, metadata, shared-link, task, Box Sign and Doc Gen events. See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/box-webhooks/references/overview.md).

> **For the full event reference**, see [Box webhook triggers](https://developer.box.com/guides/webhooks/triggers/).

## Important Headers

| Header | Description |
| --- | --- |
| `BOX-SIGNATURE-PRIMARY` | Base64 HMAC-SHA256 made with the primary signature key (the one verified) |
| `BOX-SIGNATURE-SECONDARY` | Same, made with the secondary key (ignored) |
| `BOX-DELIVERY-TIMESTAMP` | RFC 3339 send time; part of the signed message |
| `BOX-DELIVERY-ID` | Unique per delivery attempt; changes on retry |

## Environment Variables

```bash
BOX_WEBHOOK_SECRET=your_primary_signature_key   # Developer Console → Webhooks → Manage signature keys
BOX_WEBHOOK_TOLERANCE_SECONDS=0   # optional; a positive value opts in to the replay window

```

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/box-webhooks/references/overview.md) - Box V2 webhook concepts, all triggers, payloads, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/box-webhooks/references/setup.md) - Signature keys, creating a webhook, Hookdeck source setup
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/box-webhooks/references/verification.md) - Signature verification details and gotchas