> For the complete documentation index, see [llms.txt](https://docs.flowretail.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flowretail.com/docs/developers/webhooks.md).

# Webhooks

Receive events instead of polling — and the two delivery rules that surprise people.

Flow Retail can call your system when something happens — an order is settled, a product changes, stock moves. You register an endpoint against an action, and Flow Retail `POST`s a JSON payload to it.

## What a payload contains

A webhook payload is a set of **references**, not a copy of the entity. You get the action and the identifiers involved; you call the API to fetch the detail.

```json
{
  "action": "ORDER_SETTLED",
  "tenant": { "Id": 7, "Uid": "7" },
  "store": { "Id": 1, "Uid": "70" },
  "order": { "Id": 7123, "Uid": "700" },
  "tenantUser": { "Id": 7, "Uid": "9" },
  "jobId": 12345,
  "attempt": 1
}
```

Two details that break integrations if missed:

* **The nested reference keys are `Id` and `Uid`, capitalised.** The envelope keys around them (`action`, `tenant`, `order`, `jobId`, `attempt`) are lowerCamelCase. Reading `payload.order.uid` returns nothing — it is `payload.order.Uid`.
* **There is no timestamp field.** Payloads carry no `occurredAt` or equivalent. If you need event time, record your own receipt time.

Which reference fields appear depends on the action: a product event carries `product`, a stock event carries `warehouse` and `product`, a till event carries `till`. Absent fields are omitted rather than sent as null, so treat every field except `action`, `jobId` and `attempt` as optional.

`jobId` and `attempt` are added by Flow Retail as the job is dispatched. **Deduplicate on `jobId`** — retries of the same event reuse it, and `attempt` increments from 1.

## Delivery, retries and timeouts

Two of these rules are the opposite of what most APIs do. Both silently lose events if you assume otherwise.

### A timeout is treated as success

If your endpoint does not respond within **10 seconds**, Flow Retail records the delivery as successful, logs the timeout, and **drops the event**. It is not retried.

Acknowledge immediately and process asynchronously. Return `200` or `202` as soon as you have the payload safely on your own queue — never do the work inside the request.

The same applies to a connection that *times out* rather than being refused. If your host accepts the connection and then stalls, that is treated as a timeout and the event is dropped. A connection that is actively **refused** is a transport error and *is* retried. A server that is down and refusing keeps its events; a server that is hanging loses them.

### `429` is retried; other `4xx` codes are not

| Your response                  | Retried?                         |
| ------------------------------ | -------------------------------- |
| `2xx`                          | No — success                     |
| `3xx`, `4xx` other than `429`  | No — event permanently discarded |
| `429 Too Many Requests`        | **Yes**                          |
| `5xx`                          | Yes                              |
| Connection refused or reset    | Yes                              |
| Connection or response timeout | No — counted as success          |

If you are overloaded, return `429`. Returning a different `4xx` for a problem you want redelivered is the most common way integrations lose events.

### Retry schedule

Retries run up to **30 times**, with this backoff:

| Retry    | Wait before it |
| -------- | -------------- |
| 1        | 1 minute       |
| 2        | 2 minutes      |
| 3        | 5 minutes      |
| 4        | 10 minutes     |
| 5        | 20 minutes     |
| 6        | 30 minutes     |
| 7        | 45 minutes     |
| 8        | 60 minutes     |
| 9        | 90 minutes     |
| 10 to 30 | 120 minutes    |

A webhook that fails every time is retried for just under two days — about 46 hours across all 30 attempts — before it is given up on.

## Securing your endpoint

Flow Retail does **not** sign webhook payloads. There is no HMAC header and no signature to verify.

Instead, you secure the endpoint with a credential that you choose when you register the webhook's HTTP transport:

| `securityType` | What Flow Retail sends                                              |
| -------------- | ------------------------------------------------------------------- |
| `NONE`         | No credential                                                       |
| `BASIC_AUTH`   | `Authorization: Basic …` from the `username` and `password` you set |
| `BEARER_TOKEN` | `Authorization: Bearer …` from the `token` you set                  |

You can also attach custom headers to the transport.

Use `BEARER_TOKEN` or `BASIC_AUTH`, and reject any request to your webhook endpoint that does not carry the credential you configured. Because there is no signature, that credential is the only thing distinguishing a real delivery from anyone else who has guessed your URL — treat it as a secret and rotate it like one.

## Available actions

There are 21 actions you can subscribe to.

| Action                          | Fires when                                                              |
| ------------------------------- | ----------------------------------------------------------------------- |
| `PRODUCT_CREATE`                | A product is created                                                    |
| `PRODUCT_UPDATE`                | A product is updated                                                    |
| `PRODUCT_DELETE`                | A product is deleted                                                    |
| `PRODUCT_MEDIA_CHANGE`          | Product media is created, updated or deleted                            |
| `CUSTOMER_CREATE`               | A customer is created                                                   |
| `CUSTOMER_UPDATE`               | A customer is updated                                                   |
| `CUSTOMER_DELETE`               | A customer is deleted                                                   |
| `ORDER_SETTLED`                 | An order is fully paid                                                  |
| `ORDER_STATE_CHANGE`            | An order changes state                                                  |
| `ORDER_DELIVERED`               | Every line on an order is delivered                                     |
| `ORDER_LINE_DELIVERED`          | A single order line is delivered                                        |
| `ORDER_LINE_RESERVATION_CHANGE` | An order line's reservation changes                                     |
| `ORDER_RECEIPT_SETTLED`         | A receipt is settled                                                    |
| `ORDER_HANDLING_STATE_CHANGED`  | Handling moves to `NOT_STARTED`, `IN_PROGRESS`, `ON_HOLD` or `COMPLETE` |
| `PRICE_CREATE`                  | A price is created                                                      |
| `PRICE_UPDATE`                  | A price is updated                                                      |
| `PRICE_DELETE`                  | A price is deleted                                                      |
| `STOCK_CHANGE`                  | Stock changes for a warehouse and product                               |
| `PURCHASE_RECEIVED`             | A purchase order is fully received                                      |
| `TILL_OPEN`                     | A till is opened                                                        |
| `TILL_CLOSED`                   | A till is counted and closed                                            |

## Example payloads

### PRODUCT\_UPDATE

```json
{
  "action": "PRODUCT_UPDATE",
  "tenant": { "Id": 7, "Uid": "7" },
  "product": { "Id": 123, "Uid": "PRD-123" },
  "tenantUser": { "Id": 7, "Uid": "9" },
  "jobId": 12346,
  "attempt": 1
}
```

Fetch the product itself from the product endpoints using `product.Uid`.

### STOCK\_CHANGE

```json
{
  "action": "STOCK_CHANGE",
  "tenant": { "Id": 7, "Uid": "7" },
  "warehouse": { "Id": 12, "Uid": "WH-01" },
  "product": { "Id": 123, "Uid": "PRD-123" },
  "jobId": 12347,
  "attempt": 1
}
```

The payload tells you *that* stock changed, not the new level. Read the current figure from the stock endpoints.

## Building a reliable receiver

1. **Verify the credential** you configured on the transport, and reject anything without it.
2. **Persist the payload and return `200` immediately.** You have 10 seconds, and a timeout is not retried.
3. **Deduplicate on `jobId`** before processing.
4. **Fetch the detail from the API** using the `Uid` values.
5. **Return `429` when overloaded** — it is the only `4xx` that gets redelivered.
6. **Expect new fields over time.** Parse only what you use and ignore the rest.

Stuck on a delivery? Send us the `jobId` and [we will trace it](/docs/developers/errors-and-troubleshooting.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.flowretail.com/docs/developers/webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
