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 — push sales into Flipdish
- Menu Management API — read menus and revisions
- Operations Updates API — report menu publish status and item / store snooze
- Org Management API — enumerate orgs, properties, sales channels
- Webhook Service API — manage subscriptions
- Webhooks (v3) — see Subscribe to Flipdish Events (v3) for the delivery contract and signature verification
AudienceEngineers 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.
BetaSeveral v3.0 endpoints used here (Sales API, Operations Updates, v3 webhooks) are currently in closed beta. Email [email protected] 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. sales.created.v1).
Prerequisites
Before writing any code:
- Request API access by emailing [email protected]. You will receive an API key and an
orgIdto test against. - Read The Flipdish Order Flow to understand the sale lifecycle.
- Read Subscribe to Flipdish Events (v3) for the webhook delivery contract, headers, and signature verification.
- Pick a User-Agent and follow User Agents.
Authentication
All v3.0 endpoints use an API key passed in the FD-Authorization header:
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:
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 ──sales.created.v1 / sales.pos.status.updated.v1
/ sales.delivery.status.updated.v1 / sales.cancelled.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 onsaleIdfor sale events). Webhooks are at-least-once. - Use
externalIdon 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 toGET /orgManagement/orgs/{orgId}/properties— list properties (locations) under an orgGET /orgManagement/orgs/{orgId}/properties/{propertyId}/salesChannels— list the sales channels on a property; pick the ones for yoursalesChannelType
Persist the mapping per linked store:
{
"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
menu.published.v1When 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:
{
"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:
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 revisionGET /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:
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:
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": "[email protected]",
"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 viamenu.published.v1(or pulled via Menu Management).externalId— your reference. Flipdish includes it on every related sale webhook.dispatchType—DineIn,TakeAway,Collection, orDelivery.payments[]— list how the customer paid. Sum of payments should equal the total of items + charges − discounts.requestedFulfillmentTime— ISO-8601 UTC. IfdesiredAsapis 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 |
|---|---|
sales.created.v1 | A sale was created (mirror of your push, useful for audit / replay) |
sales.pos.status.updated.v1 | Kitchen / POS state changed (e.g. SALE_PREPARED_BY_KITCHEN) |
sales.delivery.status.updated.v1 | Delivery state changed (e.g. SALE_DISPATCHED), with dispatchTime |
sales.cancelled.v1 | Sale was cancelled, with cancellationReason and cancellationNotes |
Example of sales.pos.status.updated.v1:
{
"source": "rms",
"detail-type": "sales.pos.status.updated.v1",
"detail": {
"properties": {
"orgId": "org123",
"brandId": "br123",
"propertyId": "p123",
"salesChannelId": "sc123",
"salesChannelType": "ExternalApp",
"saleId": "sale-123",
"externalId": "your-platform-order-12345",
"status": "SALE_PREPARED_BY_KITCHEN"
},
"metadata": { "actor": { "id": "user-123", "email": "[email protected]", "name": "Jane" } }
}
}Add these to your subscription:
{
"eventTypes": [
"menu.published.v1",
"menu.validation.failed.v1",
"menu.item.snoozed.v1",
"menu.item.unsnoozed.v1",
"sales_channel.snoozed.v1",
"sales_channel.unsnoozed.v1",
"sales.created.v1",
"sales.cancelled.v1",
"sales.pos.status.updated.v1",
"sales.delivery.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, withmenuId,menuRevisionId,menuItemId, optionalexpiry.sales_channel.snoozed.v1/sales_channel.unsnoozed.v1— entire sales channel (your marketplace listing for that property), with optionalexpiry.
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:
# 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:
X-Flipdish-Event-Type: sales.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:
- Read the raw request body — do not parse JSON before verifying.
- Compute
HMAC-SHA256( secret, "${timestamp}.${rawBody}" ). - Compare the result to the
sha256=value using a constant-time comparison. - Reject with
401on mismatch.
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
eventIdfromX-Flipdish-Idempotency-Key(when present), or onsaleId+ event type for sale events. - 10-second budget. If your handler can't finish in 10 seconds, persist the event and process it asynchronously.
externalIdon the way in. Use it as your idempotency key onPOST /sales/orgs/{orgId}/salesretries.- 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}withenabled: true. - 3xx is not success. Only
2xxcounts.
Testing
The Webhook Service API includes a synthetic test trigger:
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 sales.created.v1 followed by sales.pos.status.updated.v1 as the merchant's POS / kitchen progresses.
Pre-launch checklist
- API key stored securely server-side; sent as
FD-Authorization(orAuthorizationon the Webhook Service API) -
User-Agentset on every request - Per-merchant mapping persisted:
orgId,propertyId,salesChannelId, your store id - Subscribed to
menu.published.v1(andmenu.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
sales.created.v1,sales.cancelled.v1,sales.pos.status.updated.v1,sales.delivery.status.updated.v1 - Subscribed to
menu.item.snoozed.v1/unsnoozed.v1andsales_channel.snoozed.v1/unsnoozed.v1 -
POST /sales/orgs/{orgId}/salesintegrated with correctsalesChannelId,menuId,menuRevisionId,dispatchType, andpayments -
externalIdset 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
2xxwithin 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) — full webhook delivery contract and signature verification
- The Flipdish Order Flow — sale lifecycle reference
- API Reference (v3.0) — every v3 endpoint and model
- User Agents
