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

# Webhook

> POST scheduled reports to your own endpoint as JSON, with optional request signing

Choose **Webhook** on a schedule to POST each run to a URL you control, as JSON. It is the channel for wiring reports into your own systems, a data warehouse, or a no-code tool like Zapier or Make.

## Setup

Enter your endpoint's **URL**. On each run Stockful sends a `POST` with `Content-Type: application/json`.

Optionally generate a **signing secret** so your endpoint can verify a request came from Stockful (see [Verifying requests](#verifying-requests)). The secret is shown once, so copy it then. Regenerate it if you lose it.

**[Send now](/user-guide/reports/scheduling#send-now)** works for webhook schedules, so you can fire a test delivery on demand.

## Payload

The body is an envelope carrying the schedule, delivery metadata, and the report(s):

```json theme={null}
{
  "schedule": { "id": 21, "name": "Weekly low stock", "frequency": "weekly", "createdBy": "Jane Doe" },
  "generatedAt": "2026-08-03T07:00:00.000Z",
  "reports": [
    {
      "type": "low_stock",
      "name": "Low Stock",
      "rowCount": 128,
      "columns": [
        { "key": "sku", "label": "SKU", "type": "text", "format": null },
        { "key": "days_of_supply", "label": "Days of supply", "type": "numeric", "format": "number" }
      ],
      "dataFormat": "json",
      "data": [{ "sku": "ABC-1", "days_of_supply": 4 }]
    }
  ]
}
```

`reports` is an array, so a future multi-report schedule can add entries without changing the shape. Each report's `columns` describe its fields: `type` is `text` or `numeric`, and `format` carries the finer type (`currency`, `percent`, `date`, and so on), or `null` for a plain dimension.

## Inline data or a linked file

`dataFormat` on each report tells you where the rows are, so a receiver never has to inspect the keys:

* **`json`** means the rows are inline in `data`, an array of objects.
* **`jsonl`** means the report was too large to send inline (over about 1 MB), so `data` is replaced by **`dataUrl`**: a link to the full report as JSONL, one JSON object per line. Fetch it to read the rows.

```js theme={null}
const rows =
  report.dataFormat === "json"
    ? report.data
    : await fetchJsonl(report.dataUrl);
```

The `dataUrl` link is tokened and served as `application/x-ndjson`.

## Verifying requests

If the schedule has a signing secret, each POST carries an **`X-Stockful-Signature`** header: the HMAC-SHA256 of the raw request body, hex-encoded, keyed with your secret. Recompute it and compare to trust the payload:

```
X-Stockful-Signature == hex( hmac_sha256(secret, rawRequestBody) )
```

Sign the raw request body bytes, before any JSON parsing, so they match what Stockful signed.
