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

# Webhooks

> Receive extracted results as signed HTTPS POST requests to your endpoint.

Set the mailbox's **Output** to `WEBHOOK` or `BOTH`, then fill in the Webhook section of the mailbox editor. Every completed extraction is POSTed to your URL as JSON.

## Requirements

* The URL must be **public HTTPS** — localhost and private addresses are rejected.
* Your endpoint must respond with a `2xx` status **within 10 seconds**.

## Payload

```json theme={null}
{
  "id": "em_01JAB7Q2M3N4P5Q6R7S8T9V0W1",
  "mailbox": "inv-7k2f@bouncebox.app",
  "received_at": "2026-09-16T10:24:03.512Z",
  "from": "billing@acmesupplies.com",
  "subject": "Invoice INV-2041 from Acme Supplies",
  "schema_template": "invoice",
  "attachments": [
    {
      "filename": "inv-2041.pdf",
      "contentType": "application/pdf",
      "size": 81234,
      "url": "https://signed-download.example/inv-2041.pdf"
    }
  ],
  "data": {
    "invoice_number": "INV-2041",
    "amount_total": 1190.0,
    "currency": "EUR"
  }
}
```

* `data` is the extracted result — exactly what the **EXTRACTED RESULT** panel shows.
* `attachments[].url` is a signed download link valid for **7 days**, or `null` if the file can't be shared. Download eagerly; don't store the link.

## Headers

Every POST includes:

| Header                  | Value                                                                             |
| ----------------------- | --------------------------------------------------------------------------------- |
| `Content-Type`          | `application/json`                                                                |
| `X-BounceBox-Signature` | `sha256=<hex>` — HMAC-SHA256 of the exact raw body, keyed with the signing secret |
| *(your headers)*        | Any custom headers configured in **Headers (JSON object)**                        |

## Verifying the signature

```js theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook(rawBody, signatureHeader, secret) {
  if (typeof signatureHeader !== "string") return false;
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
  return (
    expected.length === signatureHeader.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
  );
}
```

The signature is computed over the **exact raw body** — verify it before you parse the JSON. The signing secret is your workspace secret unless the mailbox sets its own.

<Warning>
  Check `X-BounceBox-Signature` over the raw request body **before** parsing and acting on the payload, and reject mismatches with a non-2xx status. The signature proves the request is authentic and untampered, but it does not prevent replays — the payload carries no timestamp. Protect your endpoint by tracking payload IDs and ignoring duplicates, and only configure public HTTPS URLs.
</Warning>

## Custom body template

Leave **Body template** empty to get the canonical payload above. To send a custom shape, write a template using placeholders:

* `{{mailbox}}`, `{{received_at}}`, `{{email_id}}`, `{{from}}`, `{{subject}}` — email metadata
* `{{attachments}}` — the attachment list
* `{{data}}` — the extracted JSON

Missing fields render as empty strings.

## Retries

* Up to **4 attempts** per webhook delivery.
* A `2xx` within 10 seconds is success. Anything else — a `5xx`, a timeout, or a network error — is retried with a growing backoff: about 1s, 4s, 9s, 16s.
* A `4xx` response fails delivery **immediately** without retry — a rejected request won't get better by waiting.

Every attempt lands in the mailbox's [delivery log](/dashboard/inbox).
