---
updatedAt: 2026-08-12T16:31:04.000Z
---

Fetch the complete documentation index at: https://developers.flipdish.com/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Build a marketplace integration

How to integrate a third-party marketplace (delivery aggregator, ordering platform) with Flipdish using the v3.0 public API and webhooks

## Introduction

This guide is for third-party **marketplaces** — delivery aggregators, ordering platforms, virtual brand operators — who want to push sales into Flipdish on behalf of merchants, keep their menu in sync with the merchant's source of truth, and report availability and fulfilment status back.

You will work entirely against Flipdish's **v3.0** public surface area:

* **REST API** at `https://api.flipdish.co`
  * [Sales API](https://developers.flipdish.com/v3.0/reference/) — push sales into Flipdish
  * [Menu Management API](https://developers.flipdish.com/v3.0/reference/) — read menus and revisions
  * [Operations Updates API](https://developers.flipdish.com/v3.0/reference/) — report menu publish status and item / store snooze
  * [Org Management API](https://developers.flipdish.com/v3.0/reference/) — enumerate orgs, properties, sales channels
  * [Webhook Service API](https://developers.flipdish.com/v3.0/reference/) — manage subscriptions
* **Webhooks (v3)** — see [Subscribe to Flipdish Events (v3)](/docs/webhooks-v3) for the delivery contract and signature verification

> 📘 Audience
>
> Engineers at a marketplace platform building an integration that links Flipdish merchants to their delivery / ordering surface. Familiarity with HTTP, JSON, and HMAC signatures is assumed.

> 🚧 Beta
>
> Several v3.0 endpoints used here (Sales API, Operations Updates, v3 webhooks) are currently in **closed beta**. Email <integrations@flipdish.com> to request access and credentials.

## Concept model

| Concept           | Format                                                 | Notes                                                                                                             |
| ----------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| **Org**           | `org123`                                               | Top-level tenant — a chain, franchise, or independent business.                                                   |
| **Brand**         | `br123`                                                | A trading brand within an org.                                                                                    |
| **Property**      | `p123`                                                 | A physical location (a venue / store).                                                                            |
| **Sales channel** | `sc123` with `salesChannelType` (e.g. `ExternalApp`)   | Where orders flow through. Your marketplace will be represented as a sales channel on each property that opts in. |
| **Menu**          | UUID, with numeric `revisionId`                        | Owned by an org and published to one or more sales channels.                                                      |
| **Sale**          | `saleId` (e.g. `AF34FV`) plus an optional `externalId` | Your reference to the order on your platform.                                                                     |

Webhook event types are versioned with a `.v1` suffix (e.g. `sale.created.v1`).

## Prerequisites

Before writing any code:

1. **Request API access** by emailing <integrations@flipdish.com>. You will receive an API key and an `orgId` to test against.
2. **Read [The Flipdish Order Flow](/docs/the-flipdish-order-flow)** to understand the sale lifecycle.
3. **Read [Subscribe to Flipdish Events (v3)](/docs/webhooks-v3)** for the webhook delivery contract, headers, and signature verification.
4. **Pick a User-Agent** and follow [User Agents](/docs/user-agents).

## Authentication

All v3.0 endpoints use an API key passed in the **`FD-Authorization`** header:

```bash
curl https://api.flipdish.co/menuManagement/orgs/org123/menus/headers \
  -H 'FD-Authorization: <your_api_key>' \
  -H 'User-Agent: <your_app_name>/<version>'
```

The Webhook Service API uses the **`Authorization`** header instead — see its reference page for details.

Rules of thumb:

* Always use HTTPS — plain HTTP is rejected.
* Treat the API key like a password: server-side only, never in mobile apps or browsers.
* Rotate keys via Flipdish if they leak.

## Architecture overview

A typical marketplace integration is built around three loops, all driven by v3.0 endpoints and webhooks:

| Loop               | Trigger                             | Direction                                  |
| ------------------ | ----------------------------------- | ------------------------------------------ |
| **Onboarding**     | Merchant enables your sales channel | Merchant → Flipdish portal → your platform |
| **Menu sync**      | `menu.published.v1` webhook         | Flipdish → your platform                   |
| **Sale lifecycle** | Customer orders on your marketplace | Your platform ↔ Flipdish                   |

End-to-end:

```text
Merchant adds your marketplace as a sales channel in the Flipdish portal
       │
       ▼
Flipdish ──menu.published.v1 webhook──▶ your platform
       │
       ▼
Your platform GETs the menu revision from Menu Management API,
transforms it, and publishes to your marketplace listing
       │
       ▼
Your platform reports back via Operations Updates API:
  POST .../menu-publish-status

Customer orders on your marketplace
       │
       ▼
Your platform ──POST /sales/orgs/{orgId}/sales──▶ Flipdish
       │
       ▼
Flipdish ──sale.created.v1 / sale.status.updated.v1──▶ your platform
       │
       ▼
Update marketplace order, dispatch driver, etc.
```

Three patterns are non-negotiable, regardless of your stack:

* **Acknowledge webhooks fast.** Verify the signature, persist the event, return `2xx`. Do real work asynchronously. Flipdish times out after 10 seconds and retries.
* **Be idempotent on `eventId` (and on `saleId` for sale events).** Webhooks are at-least-once.
* **Use `externalId` on sales** to dedupe replays from your own system — it is your reference and is round-tripped on every sale event.

## Onboarding a merchant

A merchant who lives on Flipdish opts into your marketplace from the Flipdish portal, which surfaces your integration as a sales channel on the chosen properties. Once that is done, your platform discovers what to work with using the Org Management API:

* `GET /orgManagement/orgs` — list orgs your key has access to
* `GET /orgManagement/orgs/{orgId}/properties` — list properties (locations) under an org
* `GET /orgManagement/orgs/{orgId}/properties/{propertyId}/salesChannels` — list the sales channels on a property; pick the ones for your `salesChannelType`

Persist the mapping per linked store:

```json
{
  "orgId":          "org123",
  "brandId":        "br123",
  "propertyId":     "p789",
  "salesChannelId": "sc456",
  "marketplaceStoreId": "your-platform-store-id"
}
```

## Syncing menus

The Flipdish menu is the source of truth. Pull on demand and listen for changes — never store and mutate independently.

### Listen for `menu.published.v1`

When a menu is (re)published to a sales channel that belongs to your marketplace, Flipdish fires `menu.published.v1`. The payload includes the full published snapshot so you usually don't need a follow-up GET:

```json
{
  "orgId": "org123",
  "brandId": "br456",
  "propertyId": "p789",
  "salesChannelId": "sc123",
  "salesChannelType": "ExternalApp",
  "menuId": "123e4567-e89b-12d3-a456-426614174000",
  "menuRevisionId": 1,
  "menuPublishId": "223e4567-e89b-12d3-a456-426614174000",
  "menu": { /* categories, items, modifiers, pricingProfiles, charges, … */ }
}
```

Subscribe via the Webhook Service API:

```bash
curl -X POST https://api.flipdish.co/webhooks/orgs/org123/subscriptions \
  -H 'Authorization: <your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "orgId": "org123",
    "callbackUrl": "https://your-platform.example.com/flipdish/webhooks",
    "eventTypes": ["menu.published.v1", "menu.validation.failed.v1"],
    "secret": "<a strong random string you generate>",
    "propertyIds": ["p789"]
  }'
```

The `secret` is the value Flipdish will use to sign deliveries. Store it alongside the subscription.

### Pull on demand

If you need to fetch a menu directly (initial import, recovery, audit):

* `GET /menuManagement/orgs/{orgId}/menus/headers` — list menus (lightweight)
* `GET /menuManagement/orgs/{orgId}/menus/{menuId}/current` — the current published revision
* `GET /menuManagement/orgs/{orgId}/menus/{menuId}/revisions/{revisionId}` — a specific revision

The full menu endpoint is heavier than the headers endpoint — only call it when you actually need the full tree.

### What to map

| Flipdish concept          | Typical marketplace concept                                                                    |
| ------------------------- | ---------------------------------------------------------------------------------------------- |
| `category`                | Category                                                                                       |
| `category.items[]`        | Product / item                                                                                 |
| `modifier` (group)        | Modifier group                                                                                 |
| `modifier.items[]`        | Modifier option                                                                                |
| `pricingProfiles[]`       | Per-dispatch-type pricing (`collectionPrice`, `deliveryPrice`, `dineInPrice`, `takeawayPrice`) |
| `charges[]`               | Service / delivery / tip charges                                                               |
| `availabilityOverrides[]` | Day-and-time availability rules                                                                |

Use `productId` and `externalId` on items to round-trip your marketplace IDs.

### Confirming the menu was applied

After you have processed the menu and pushed it to your marketplace, report success or failure back to Flipdish so the merchant sees the publish status in the portal:

```bash
curl -X POST \
  "https://api.flipdish.co/operations-updates/orgs/org123/properties/p789/salesChannels/sc123/menu-publish-status" \
  -H 'FD-Authorization: <your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "menuPublishId": "223e4567-e89b-12d3-a456-426614174000",
    "success": true
  }'
```

On failure, set `success` to `false` and include `errorMessage`.

## Pushing sales to Flipdish

When a customer orders on your marketplace, create a sale on Flipdish via the Sales API:

```bash
curl -X POST https://api.flipdish.co/sales/orgs/org123/sales \
  -H 'FD-Authorization: <your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "salesChannelId": "sc123",
    "source": "YourMarketplace",
    "externalId": "your-platform-order-12345",
    "requestedFulfillmentTime": "2025-10-28T14:30:00Z",
    "desiredAsap": true,
    "dispatchType": "Delivery",
    "menuId": "123e4567-e89b-12d3-a456-426614174000",
    "menuRevisionId": "1",
    "customer": {
      "phoneNumber": "+353871234567",
      "name": "John Smith",
      "emailAddress": "john@example.com",
      "externalId": "customer_123"
    },
    "delivery": {
      "deliveredBy": "External",
      "notes": "Leave at door",
      "location": {
        "countryCode": "IE",
        "addressFields": { "line1": "38 Pearse Street", "line2": "Dublin", "postCode": "D02 XX00" },
        "coordinates": { "latitude": 53.343, "longitude": -6.255 }
      }
    },
    "items": [
      {
        "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5",
        "quantity": 1,
        "unitPrice": 13.99,
        "modifierItems": [
          { "menuItemId": "10a85f64-5717-4562-b3fc-2c963f66afb3", "quantity": 1, "unitPrice": 1.50 }
        ]
      }
    ],
    "charges": [ { "type": "Delivery", "amount": 2.50 } ],
    "discounts": [],
    "payments": [
      {
        "type": "Sale",
        "paymentMethod": "Online",
        "amount": 18.49,
        "paidAt": "2025-10-28T14:25:00Z",
        "description1": "Visa ****4921"
      }
    ]
  }'
```

The response gives you Flipdish's `saleId`, plus `createdAt` and `dispatchTime`.

Field rules of thumb:

* **`salesChannelId`** — the sales channel your marketplace is registered as on this property.
* **`menuId` / `menuRevisionId`** — must match the revision you consumed via `menu.published.v1` (or pulled via Menu Management).
* **`externalId`** — your reference. Flipdish includes it on every related sale webhook.
* **`dispatchType`** — `DineIn`, `TakeAway`, `Collection`, or `Delivery`.
* **`payments[]`** — list how the customer paid. Sum of payments should equal the total of items + charges − discounts.
* **`requestedFulfillmentTime`** — ISO-8601 UTC. If `desiredAsap` is true and the time is more than \~1 hour out, Flipdish will treat it as scheduled.

## Subscribing to sale events

Once a sale is in Flipdish, you keep your marketplace in sync by subscribing to the sale lifecycle events:

| Event                    | Meaning                                                                                                                                                                                                                                                                          |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sale.created.v1`        | A sale was created (mirror of your push, useful for audit / replay). Carries the full sale payload inline.                                                                                                                                                                       |
| `sale.updated.v1`        | A sale was materially updated (items, charges, or discounts changed). Carries the full sale payload inline.                                                                                                                                                                      |
| `sale.status.updated.v1` | The sale transitioned to a new lifecycle status. Unifies POS/kitchen, delivery, and cancellation into one event (e.g. `SALE_PREPARED_BY_KITCHEN`, `SALE_DISPATCHED`, `SALE_DELIVERED`, `SALE_CANCELLED`). `SALE_CANCELLED` carries `cancellationReason` and `cancellationNotes`. |

Example of `sale.status.updated.v1`:

```json
{
  "eventType": "sale.status.updated.v1",
  "eventId": "01HXYZ...",
  "eventCreatedAt": "2025-10-28T14:00:00.000Z",
  "data": {
    "orgId": "org123",
    "brandId": "br123",
    "propertyId": "p123",
    "salesChannelId": "sc123",
    "salesChannelType": "ExternalApp",
    "saleId": "sale-123",
    "externalId": "your-platform-order-12345",
    "status": "SALE_PREPARED_BY_KITCHEN",
    "saleStatusUpdatedTime": "2025-10-28T14:00:00.000Z"
  }
}
```

Add these to your subscription:

```json
{
  "eventTypes": [
    "menu.published.v1",
    "menu.validation.failed.v1",
    "menu.item.snoozed.v1",
    "menu.item.unsnoozed.v1",
    "sales_channel.snoozed.v1",
    "sales_channel.unsnoozed.v1",
    "sale.created.v1",
    "sale.updated.v1",
    "sale.status.updated.v1"
  ]
}
```

## Item and store availability

Two directions:

### Receiving snooze events from Flipdish

When a merchant marks an item or sales channel as unavailable on the Flipdish portal:

* `menu.item.snoozed.v1` / `menu.item.unsnoozed.v1` — single menu item, with `menuId`, `menuRevisionId`, `menuItemId`, optional `expiry`.
* `sales_channel.snoozed.v1` / `sales_channel.unsnoozed.v1` — entire sales channel (your marketplace listing for that property), with optional `expiry`.

Mirror these onto your marketplace listing.

### Reporting snooze status back

If a snooze on your marketplace originates from your side (e.g. driver reports an item out), report it via the Operations Updates API:

```bash
# Snooze a single menu item on this sales channel
curl -X POST \
  "https://api.flipdish.co/operations-updates/orgs/org123/properties/p789/salesChannels/sc123/item-snooze-status" \
  -H 'FD-Authorization: <your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "menuId": "123e4567-e89b-12d3-a456-426614174000",
    "menuRevisionId": "1",
    "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5",
    "requestedStatus": "Snooze",
    "success": true
  }'

# Snooze the entire sales channel
curl -X POST \
  "https://api.flipdish.co/operations-updates/orgs/org123/properties/p789/salesChannels/sc123/store-snooze-status" \
  -H 'FD-Authorization: <your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "requestedStatus": "Snooze", "success": true }'
```

`requestedStatus` is `Snooze` or `UnSnooze`.

## Verifying webhooks

Every v3 webhook delivery includes:

```http
X-Flipdish-Event-Type: sale.created.v1
X-Flipdish-Event-Version: v1
X-Flipdish-Timestamp: 2025-08-14T12:34:56.789Z
X-Flipdish-Signature: t=2025-08-14T12:34:56.789Z,sha256=<hex>
```

Verification:

1. Read the **raw** request body — do not parse JSON before verifying.
2. Compute `HMAC-SHA256( secret, "${timestamp}.${rawBody}" )`.
3. Compare the result to the `sha256=` value using a constant-time comparison.
4. Reject with `401` on mismatch.

```ts
import crypto from 'node:crypto';

export function verifySignature(
  rawBody: string,
  timestamp: string,
  signature: string,
  secret: string,
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8')
    .digest('hex');
  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(signature, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

The `secret` is the one you supplied when creating the subscription via the Webhook Service API.

## Idempotency, retries and failure handling

* **At-least-once delivery.** Flipdish retries failed webhook deliveries. Dedupe on the `eventId` from `X-Flipdish-Idempotency-Key` (when present), or on `saleId` + event type for sale events.
* **10-second budget.** If your handler can't finish in 10 seconds, persist the event and process it asynchronously.
* **`externalId` on the way in.** Use it as your idempotency key on `POST /sales/orgs/{orgId}/sales` retries.
* **Auto-disable.** Subscriptions that fail persistently are disabled. Re-create them via the Webhook Service API, or update via `POST /webhooks/orgs/{orgId}/subscriptions/{subId}` with `enabled: true`.
* **3xx is not success.** Only `2xx` counts.

## Testing

The Webhook Service API includes a synthetic test trigger:

```bash
curl https://api.flipdish.co/webhooks/orgs/org123/subscriptions/sub_<uuid>/trigger \
  -H 'Authorization: <your_api_key>'
```

This sends a synthetic delivery for every event type on the subscription, so you can validate signature verification and your routing without waiting for real activity.

For sale-side testing, push a real sale via `POST /sales/orgs/{orgId}/sales` against a staging org and confirm you receive `sale.created.v1` followed by `sale.status.updated.v1` as the merchant's POS / kitchen progresses.

## Pre-launch checklist

* [ ] API key stored securely server-side; sent as `FD-Authorization` (or `Authorization` on the Webhook Service API)
* [ ] `User-Agent` set on every request
* [ ] Per-merchant mapping persisted: `orgId`, `propertyId`, `salesChannelId`, your store id
* [ ] Subscribed to `menu.published.v1` (and `menu.validation.failed.v1`) and applying menu changes from the payload
* [ ] Menu publish status reported back via Operations Updates API on every applied / failed publish
* [ ] Subscribed to `sale.created.v1`, `sale.updated.v1`, `sale.status.updated.v1`
* [ ] Subscribed to `menu.item.snoozed.v1` / `unsnoozed.v1` and `sales_channel.snoozed.v1` / `unsnoozed.v1`
* [ ] `POST /sales/orgs/{orgId}/sales` integrated with correct `salesChannelId`, `menuId`, `menuRevisionId`, `dispatchType`, and `payments`
* [ ] `externalId` set on every sale and used as your idempotency key
* [ ] Webhook endpoint verifies HMAC over the raw body using a timing-safe compare
* [ ] Webhook endpoint returns `2xx` within 10 seconds; heavy work is queued
* [ ] Handlers idempotent on `saleId` / `eventId`
* [ ] Trigger-test (`/subscriptions/{subId}/trigger`) run end-to-end on staging
* [ ] Re-enable / monitor logic in place if a subscription is auto-disabled

## Further reading

* [Subscribe to Flipdish Events (v3)](/docs/webhooks-v3) — full webhook delivery contract and signature verification
* [The Flipdish Order Flow](/docs/the-flipdish-order-flow) — sale lifecycle reference
* [API Reference (v3.0)](https://developers.flipdish.com/v3.0/reference/) — every v3 endpoint and model
* [User Agents](/docs/user-agents)