Sales Management API
How to create, cancel, and query sales using the v3.0 Sales Management API — including the new org-wide query endpoints
BetaThe Sales Management API is currently in beta. Endpoints may change. Contact [email protected] to request access.
Introduction
The Sales Management API is the v3.0 surface for ingesting and querying sales. Use it to:
- Push sales from an external system (POS, marketplace, kiosk, ordering platform) into Flipdish so they appear in reporting, reconciliation, and the audit trail.
- Cancel a sale when a customer or restaurant voids an order.
- Update delivery status as an order moves from kitchen to customer.
- Read sales back across an entire org using the org-wide query endpoints — no
salesChannelIdneeded. The previous sales-channel-scoped read endpoints are deprecated; see Reading sales (deprecated) below.
Related guides
- Building a POS analytics or back-office integration (forecasting, scheduling, margin)? Start with Build a POS sales analytics integration — it covers the full onboarding, menu sync, and reconciliation pipeline on top of this API.
- Building a marketplace integration that also needs menu sync, item snooze, and operations status updates? See Build a marketplace integration for the end-to-end flow covering those extra surfaces.
All paths live under https://api.flipdish.co and require Authorization: Bearer <token> (OAuth2 client-credentials). See Getting Started for the token exchange.
Concept model
| Concept | Format | Notes |
|---|---|---|
| Org | org123 | Top-level tenant. Every Sales Management path is scoped to an orgId. |
| Brand | br123 — legacy: fd123, or a plain slug (e.g. mexicancafe) | The distinct identity (name, logo) associated with one or more sales channels. |
| Property | p123 | A physical location. |
| Sales channel | sc123 — legacy: a plain integer with no prefix (e.g. 123) | A named selling surface on a property (POS, kiosk, web, marketplace). Sales belong to a sales channel. |
| Sale | saleId (e.g. AF34FV, Crockford base32) + optional externalId / displayId | A transaction. saleId is a Flipdish-globally-unique short ID. externalId is your platform's opaque machine-readable reference (correlation key, round-tripped on every related event). displayId is the short human-readable label shown to staff on KDS and receipts. |
| Menu revision | UUID + numeric revisionId | Every sale references the exact menu revision it was placed against. |
Don't parse or validate ID shapes
saleId,brandId, andsalesChannelIdare opaque strings — no format is enforced, and orgs/brands/channels created before the current naming convention may not match the common shape shown above. Compare them by exact string equality only; never assume a prefix, length, or character set.
Authentication
All Sales Management endpoints use the OAuth2 Bearer token:
curl https://api.flipdish.co/salesManagement/orgs/org123/sales \
-H 'Authorization: Bearer <access_token>' \
-H 'User-Agent: <your_app_name>/<version>'Obtain the token via the client-credentials grant (see Getting Started). Send it on every request. Never send it from a browser or mobile client — server-side only.
Creating a sale
POST /salesManagement/orgs/{orgId}/salesRecords a completed sale against the org. The x-idempotency-key header is required; supply a fresh GUID (UUID v4) per logical sale. If a request with the same key was already processed, the original response is returned rather than creating a duplicate — this makes the call safe to retry on network failures.
Minimal example
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-H 'x-idempotency-key: 11111111-1111-1111-1111-111111111111' \
-H 'User-Agent: <your_app_name>/<version>' \
-d '{
"salesChannelId": "sc123",
"requestedFulfillmentTime": "2025-10-28T14:30:00Z",
"desiredAsap": true,
"dispatchType": "TakeAway",
"menuId": "123e4567-e89b-12d3-a456-426614174000",
"menuRevisionId": "23",
"items": [ { "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5", "quantity": 1, "unitPrice": 10.00, "modifierItems": [] } ],
"charges": [],
"discounts": [],
"payments": [ { "type": "Sale", "paymentMethod": "Cash", "amount": 10.00, "paidAt": "2025-10-28T14:30:00Z" } ]
}'Response:
{
"data": {
"saleId": "AF34FV",
"createdAt": "2025-10-28T14:25:10Z",
"dispatchTime": "2025-10-28T14:30:00Z"
}
}Full request with all fields
{
"salesChannelId": "sc123",
"source": "POS",
"requestedFulfillmentTime": "2025-10-28T14:30:00Z",
"desiredAsap": false,
"dispatchType": "DineIn",
"externalId": "your-platform-order-12345",
"displayId": "2A003",
"menuId": "123e4567-e89b-12d3-a456-426614174000",
"menuRevisionId": "23",
"customer": {
"name": "Jane Smith",
"contactPhoneNumber": "+353871234567",
"emailAddress": "[email protected]",
"externalId": "customer_456"
},
"dineIn": {
"tableId": "12",
"guests": 2
},
"notes": "Allergen note: contains nuts",
"items": [
{
"menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5",
"quantity": 1,
"unitPrice": 13.99,
"notes": "No onions",
"modifierItems": [
{ "menuItemId": "10a85f64-5717-4562-b3fc-2c963f66afb3", "quantity": 1, "unitPrice": 1.50 }
]
}
],
"charges": [ { "type": "Service", "amount": 1.55 } ],
"discounts": [ { "type": "Spot", "amount": 1.00 } ],
"payments": [
{
"type": "Sale",
"paymentMethod": "Credit",
"amount": 16.04,
"paidAt": "2025-10-28T14:25:00Z",
"description1": "Visa ****4921",
"description2": "VM278412312"
}
],
"metadata": "{\"posTerminal\":\"till-2\"}"
}Field reference
| Field | Required | Notes |
|---|---|---|
salesChannelId | Yes | The channel this sale belongs to. |
requestedFulfillmentTime | Yes | ISO 8601 UTC. When the restaurant hands over the food. |
dispatchType | Yes | DineIn | TakeAway | Collection | Delivery. Note capital A in TakeAway. |
menuId / menuRevisionId | Yes | Must match the published revision in use. Pass the revision the customer ordered against. |
items[] | Yes (min 1) | Recursive — modifierItems[] follow the same shape. menuItemId + quantity + unitPrice required per item. |
charges[] | Yes (can be empty) | type ∈ Delivery, Service, Tip, Other. Optional itemId for line-level charges. |
discounts[] | Yes (can be empty) | type ∈ Voucher, Loyalty, Spot, Other. code required when type is Voucher or Loyalty. |
payments[] | Yes (can be empty — implies unpaid) | type ∈ Sale, Refund. paymentMethod ∈ Cash, Credit, Online, PhonePayment, ExternalPayment. |
externalId | Recommended | Your platform's own opaque reference for this sale (e.g. a marketplace order UUID or POS ticket ID). Stored by Flipdish and included in webhook events that carry a sale object (sale.created.v1 and sale.updated.v1) — use it to correlate Flipdish sales back to your system. Not shown to restaurant staff by default. If displayId is omitted, externalId is used as the fallback back-of-house display label — so if externalId is a long UUID, also supply a short displayId. |
displayId | No | Short, human-readable label shown to restaurant staff on kitchen display systems (KDS) and printed on receipts (max 15 chars). Unlike externalId (an opaque machine reference), displayId is designed to be glanced at quickly — for example 2A003. |
source | No | Free text. E.g. POS, App, ChatGPT. |
desiredAsap | No | If true but requestedFulfillmentTime is more than ~1 hour out, it is overridden to false. |
customer | No | Use contactPhoneNumber (E.164 format, e.g. +353871234567). The phoneNumber field is deprecated. |
delivery | Conditional | Required when dispatchType is Delivery. Set deliveredBy to Client (restaurant-managed) or External (third-party driver). Include location when deliveredBy is Client. |
dineIn | Conditional | Required when dispatchType is DineIn. Supply tableId (string) and guests (int). |
notes | No | Free-text note for restaurant/kitchen staff (e.g. an allergen warning). Not shown to the customer. Distinct from delivery.notes, which carries the customer's own instructions. |
metadata | No | Free-text JSON string for any partner-specific fields. |
Sale object schema
The shapes above (salesChannelId, items, charges, …) form the PublicSale object — one consistent schema used in three places:
- The request body for
POST .../sales. - The response body for every org-wide
GETsale endpoint (the deprecated channel-scopedGETendpoints return the same shape). - The
salefield embedded insale.created.v1/sale.updated.v1webhook events (see Sale webhook events).
This section documents every field of the nested customer, delivery, dineIn, items, charges, discounts, and payments objects. The top-level fields are covered in the field reference above.
customer
customerOptional. Identifies the end customer placing the sale.
| Field | Type | Notes |
|---|---|---|
id | UUID | Flipdish customer UUID. If provided, must reference an existing customer — omit or leave empty if unknown. |
externalId | string | The integrating partner's own identifier for this customer in their system (e.g. the customer's ID in the marketplace or ordering platform that submitted the sale). Use this to carry your platform's customer reference alongside the sale. Distinct from id, which is the Flipdish-internal customer UUID. |
name | string | Customer's full name. |
contactPhoneNumber | string | E.164 format with a leading + (e.g. +353871234567). The legacy phoneNumber field (no +, no formatting) is deprecated — use contactPhoneNumber for new integrations. See contactPhoneNumber availability for when this field is populated. |
contactMaskingCode | string | Numeric code some marketplaces (e.g. Deliveroo) issue alongside a relay phone number. Entering this code when calling the relay number routes the call through to the customer's real phone. Only present when the originating marketplace obfuscates the customer's number. |
emailAddress | string | Customer email address. See emailAddress availability for when this field is populated. |
contactPhoneNumber availability
contactPhoneNumber availabilitycontactPhoneNumber is always in E.164 format (leading +, e.g. +353871234567). Local-format numbers (with a leading 0) cannot be normalised to E.164 without a country code and are dropped.
When contactPhoneNumber IS present:
| Scenario | Detail |
|---|---|
| Integrator-submitted sale | The integrator supplied contactPhoneNumber (or legacy phoneNumber) in the Create/Update Sale request body. |
| Marketplace order with international-format phone | UberEats, Deliveroo, JustEat, etc. forward a phone already in international format (no leading 0). |
When contactPhoneNumber is ABSENT:
| Scenario | Detail |
|---|---|
customer object omitted | No customer data at all — contactPhoneNumber cannot exist. |
| Customer present, no phone | Name/email/ID provided but no phone was collected. |
Local-format phone (leading 0) | e.g. UK 07533 006 408 — cannot safely normalise to E.164 without a country code, so dropped. contactMaskingCode may still be set. |
| Marketplace relay/masking only | Platforms like Deliveroo obfuscate the real number; only a relay number + masking code is provided. The relay is not the customer's real number, so contactPhoneNumber is absent. |
| Kiosk / self-service | FlipdishKIOSK/KIOSK — customers don't enter contact details. |
| Dine-in | Phone not typically collected. |
emailAddress availability
emailAddress availabilityWhen emailAddress IS present:
| Scenario | Detail |
|---|---|
| Integrator-submitted sale | The integrator supplied emailAddress in the Create/Update Sale request body. |
| Flipdish-powered Web or App order | The customer placed the order through a Flipdish-powered channel and has a registered email address. |
| Marketplace with email sharing | Some marketplace integrations forward the customer's email if their platform exposes it. |
When emailAddress is ABSENT:
| Scenario | Detail |
|---|---|
| Marketplace without email sharing | Most major marketplaces (UberEats, Deliveroo, JustEat) do not share customer email addresses for privacy/contractual reasons — emailAddress will be absent for these orders. |
| Guest checkout / no registered email | The customer completed the order without providing an email address. |
| Kiosk / dine-in | Customer contact details are typically not collected. |
delivery
deliveryRequired when dispatchType is Delivery.
| Field | Type | Notes |
|---|---|---|
deliveredBy | enum | Client — the restaurant fulfils the delivery itself. External — a marketplace or third-party courier delivers. |
location | object | The drop-off address. Required when deliveredBy is Client — not needed for External, since the courier's own platform handles routing. See delivery.location below. |
notes | string | Free-text delivery/prep instructions from the customer (e.g. "Leave at door, don't ring the bell"). Distinct from the sale-level notes field, which is for restaurant/kitchen staff rather than the customer. |
delivery.location
delivery.location| Field | Type | Notes |
|---|---|---|
addressFields.line1 | string | Required. First address line. |
addressFields.line2 / addressFields.line3 | string | Optional further address lines. |
addressFields.postCode | string | Postal code. |
countryCode | string | Required. ISO 3166-1 alpha-2 country code (e.g. IE). |
coordinates.latitude / coordinates.longitude | number | Optional geographic coordinates for the drop-off point. |
dineIn
dineInRequired when dispatchType is DineIn.
| Field | Type | Notes |
|---|---|---|
tableId | string | Table identifier (free text). |
guests | integer | Number of guests, >= 0. |
items[]
items[]At least one item is required. Items nest recursively: a modifierItems[] entry has the exact same shape as its parent item, so any depth of modifier (e.g. pizza → size → topping → extra-cheese) is expressed as nested items rather than a separate schema.
| Field | Type | Notes |
|---|---|---|
menuItemId | UUID | Must match an item in the menu/revision referenced by the sale's menuId/menuRevisionId. |
quantity | integer | > 0. |
unitPrice | number | Price of one unit of this item (or modifier). |
notes | string | Free-text instruction for this line, e.g. "No onions please". |
modifierItems[] | array | Modifiers applied to this item — same schema as items[], so entries can themselves carry modifierItems[]. |
charges[]
charges[]Required on create, but the array can be empty. Additional monetary lines beyond item prices.
| Field | Type | Notes |
|---|---|---|
type | enum | Delivery, Service, Tip, Other. |
amount | number | >= 0. |
itemId | UUID | Optional. Set only when the charge is attached to a specific item (e.g. a bag or container deposit) rather than the whole sale. Omit for sale-level charges (e.g. an overall service charge). |
discounts[]
discounts[]Required on create, but the array can be empty. Discounts always apply at the sale level — there is no per-line discount. Every discount carries amount (number, >= 0) and type; whether code is required depends on type:
type | code | Notes |
|---|---|---|
Voucher | Required | The voucher code the customer redeemed. |
Loyalty | Required | The loyalty program code. |
Spot | Optional | An ad-hoc/manual discount applied in the moment; code is just a label if supplied. |
Other | Optional | Catch-all category; code is a free-text label if supplied. |
payments[]
payments[]Required on create, but the array can be empty — an empty array means the sale is unpaid. A refund is recorded as its own line rather than mutating the original payment: expect a Sale-type line for the original charge and a separate Refund-type line when money is returned.
| Field | Type | Notes |
|---|---|---|
type | enum | Sale (money collected) or Refund (money returned to the customer). |
paymentMethod | enum | Cash, Credit, Online, PhonePayment, ExternalPayment. |
amount | number | >= 0. |
paidAt | ISO 8601 UTC | Required. When the payment was made. |
description1 | string | Free-text payment detail, e.g. Visa ****4921. |
description2 | string | Free-text secondary detail, e.g. an authorization code or reference number. |
Cancelling a sale
POST /salesManagement/orgs/{orgId}/sales/{saleId}/cancelMarks an existing sale as cancelled. Cancellation is final — create a new sale if the customer reorders.
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales/AF34FV/cancel \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-H 'User-Agent: <your_app_name>/<version>' \
-d '{
"salesChannelId": "sc123",
"cancellationReason": "Cancelled by customer",
"cancellationNotes": "Customer called to cancel — out of area"
}'cancellationReason is required. Allowed values: Cancelled by customer, Cancelled by restaurant, Cancelled by marketplace, Cancelled by integration partner, Cancelled by system, Other. cancellationNotes is free text (max 255 chars) and is round-tripped on the sales.cancelled.v1 webhook event.
Updating delivery status
POST /salesManagement/orgs/{orgId}/sales/{saleId}/deliveryStatusRecords a delivery state transition as the order progresses through fulfilment. Send transitions in order.
# Mark as dispatched (left kitchen with driver)
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales/AF34FV/deliveryStatus \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-H 'User-Agent: <your_app_name>/<version>' \
-d '{ "salesChannelId": "sc123", "status": "SALE_DISPATCHED" }'
# Mark as delivered (customer received)
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales/AF34FV/deliveryStatus \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-H 'User-Agent: <your_app_name>/<version>' \
-d '{ "salesChannelId": "sc123", "status": "SALE_DELIVERED" }'status is SALE_DISPATCHED or SALE_DELIVERED. Both transitions surface in operational dashboards and downstream reporting.
Reading sales (deprecated)
DeprecatedThe sales-channel-scoped read endpoints below are deprecated and have been removed from this reference — they no longer appear in the sidebar or in
reference/sales-management-api.json's navigation. They remain callable for existing integrations and will keep working, but won't receive further updates. New integrations should use the org-wide query endpoints instead, which cover the same use cases without requiring asalesChannelId.
GET /salesManagement/orgs/{orgId}/salesChannels/{salesChannelId}/sales?fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD(formerly "List sales by date range") → use List sales (org-wide, full payload).GET /salesManagement/orgs/{orgId}/salesChannels/{salesChannelId}/sales/{saleId}(formerly "Get a sale") → use Get a sale (org-wide).
Org-wide queries (Beta)
BetaThese endpoints are currently in beta — they may change without notice.
The org-wide endpoints do not require a salesChannelId. Use them when you need to query or look up sales across all channels of an org (or scoped to one property), or when you don't know which sales channel a sale belongs to. Every org-wide list/get endpoint returns the full payload — header fields plus the nested sale object — in one call; there's no separate headers-only variant.
List sales (org-wide, full payload)
GET /sales/orgs/{orgId}/salesReturns paginated full sale payloads across all channels of the org, sorted by creation time descending. All query parameters are optional and combinable.
| Parameter | Type | Notes |
|---|---|---|
pageSize | 50–200 | Number of records per page. Default 50. |
cursor | string | Opaque pagination token from the previous response's nextCursor. |
after / before | ISO 8601 date-time | Bound the creation time range. |
# Most-recent 50 sales across the org
curl "https://api.flipdish.co/sales/orgs/org123/sales" \
-H 'Authorization: Bearer <access_token>' \
-H 'User-Agent: <your_app_name>/<version>'
# Last 24 hours
curl "https://api.flipdish.co/sales/orgs/org123/sales?after=2025-10-27T00:00:00Z&before=2025-10-28T00:00:00Z" \
-H 'Authorization: Bearer <access_token>' \
-H 'User-Agent: <your_app_name>/<version>'Response:
{
"pageSize": 50,
"hasMoreRecords": true,
"nextCursor": "<opaque token>",
"data": [
{
"saleId": "AF34FV",
"orgId": "org123",
"brandId": "br123",
"propertyId": "p789",
"salesChannelId": "sc123",
"salesChannelType": "POS",
"externalId": "your-platform-order-12345",
"source": "POS",
"dispatchType": "DineIn",
"status": "created",
"createdAt": "2025-10-28T14:25:10Z",
"updatedAt": "2025-10-28T14:25:10Z",
"sale": {
"salesChannelId": "sc123",
"source": "POS",
"requestedFulfillmentTime": "2025-10-28T14:30:00Z",
"dispatchType": "DineIn",
"menuId": "123e4567-e89b-12d3-a456-426614174000",
"menuRevisionId": "23",
"items": [ { "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5", "quantity": 1, "unitPrice": 13.99, "modifierItems": [] } ],
"charges": [],
"discounts": [],
"payments": [ { "type": "Sale", "paymentMethod": "Cash", "amount": 13.99, "paidAt": "2025-10-28T14:25:00Z" } ]
}
}
]
}Paginating: when hasMoreRecords is true, pass nextCursor as the cursor query parameter on the next request together with the same filter parameters. Cursors are not portable across different endpoints (org-wide vs. property-scoped) or different filter combinations.
List sales for a property (full payload)
GET /sales/orgs/{orgId}/properties/{propertyId}/salesSame shape and query parameters (pageSize, cursor, after, before) as List sales (org-wide, full payload), scoped to one property.
curl "https://api.flipdish.co/sales/orgs/org123/properties/p789/sales?pageSize=100" \
-H 'Authorization: Bearer <access_token>' \
-H 'User-Agent: <your_app_name>/<version>'
Cursor portabilityCursors are tied to both the endpoint and the filters used when they were issued — a cursor from the org-wide list isn't portable to the property-scoped list (or vice versa), and cursors aren't portable between different
propertyIdfilters.
Response fields (org-wide header)
Every item in data above wraps a SaleHeaderResponse plus a nested sale object (the full Sale object schema):
| Field | Type | Notes |
|---|---|---|
saleId | string | Flipdish sale identifier. |
orgId / brandId / propertyId | string | The org, brand, and property the sale belongs to. |
salesChannelId / salesChannelType | string / enum | The channel the sale was placed on, and its type (POS, KIOSK, UberEats, Deliveroo, etc). |
externalId | string | Partner/platform reference, if supplied at create time. |
source | string | Free-text origin set at create time (e.g. POS, App). |
dispatchType | string | Fulfilment mode: DineIn, TakeAway, Collection, or Delivery. |
status | enum | created | preparedByKitchen | dispatched | onTheWay | delivered | cancelled. |
createdAt / updatedAt | ISO 8601 UTC | When the sale was created, and when it last changed. |
cancelledAt | ISO 8601 UTC | Only present once the sale has been cancelled. |
Get a sale (org-wide)
GET /sales/orgs/{orgId}/sales/{saleId}Returns the full projected sale for the given org and saleId without requiring a salesChannelId. Use this when you have a saleId from a webhook event and don't want to look up which sales channel it belongs to.
curl https://api.flipdish.co/sales/orgs/org123/sales/AF34FV \
-H 'Authorization: Bearer <access_token>' \
-H 'User-Agent: <your_app_name>/<version>'Response — the header fields plus a nested sale object (the full Sale object schema):
{
"saleId": "AF34FV",
"orgId": "org123",
"brandId": "br123",
"propertyId": "p789",
"salesChannelId": "sc123",
"salesChannelType": "POS",
"externalId": "your-platform-order-12345",
"source": "POS",
"dispatchType": "DineIn",
"status": "created",
"createdAt": "2025-10-28T14:25:10Z",
"updatedAt": "2025-10-28T14:25:10Z",
"sale": {
"salesChannelId": "sc123",
"source": "POS",
"requestedFulfillmentTime": "2025-10-28T14:30:00Z",
"desiredAsap": false,
"dispatchType": "DineIn",
"externalId": "your-platform-order-12345",
"displayId": "2A003",
"menuId": "123e4567-e89b-12d3-a456-426614174000",
"menuRevisionId": "23",
"customer": {
"name": "Jane Smith",
"contactPhoneNumber": "+353871234567"
},
"dineIn": { "tableId": "12", "guests": 2 },
"items": [
{ "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5", "quantity": 1, "unitPrice": 13.99, "modifierItems": [] }
],
"charges": [ { "type": "Service", "amount": 1.55 } ],
"discounts": [],
"payments": [
{ "type": "Sale", "paymentMethod": "Credit", "amount": 15.54, "paidAt": "2025-10-28T14:25:00Z" }
]
}
}cancelledAt is omitted here since the sale hasn't been cancelled; it appears (ISO 8601 UTC) once status is cancelled.
Webhook events
Flipdish fires sale-related webhook events for every sale created via this API. See Sale webhook events for event schemas, payload structure, and how to subscribe.
Idempotency and retries
| Concern | How to handle it |
|---|---|
| Duplicate creates | Supply a unique x-idempotency-key (GUID) per intended sale. Retrying with the same key returns the original saleId response. |
| Duplicate webhooks | Dedupe on saleId + event type (or on eventId from X-Flipdish-Idempotency-Key if present). Webhooks are at-least-once. |
| Missed webhooks | Run a periodic reconciler using the org-wide list endpoints. |
| Webhook timeout | Return 2xx within 10 seconds. Persist and process asynchronously. Only 2xx counts — redirects do not. |
| Disabled subscriptions | Subscriptions that fail persistently are auto-disabled. Re-enable via POST /webhooks/orgs/{orgId}/subscriptions/{subId} with { "enabled": true }. |
Pre-launch checklist
- Access token obtained via client-credentials grant and stored securely server-side
-
Authorization: Bearer <access_token>sent on every request -
User-Agentheader set on every request - Per-sale
x-idempotency-key(fresh GUID) sent on everyPOST .../salescall -
externalIdset on every sale for round-trip deduplication -
menuIdandmenuRevisionIdmatch the published revision in use -
dispatchType,delivery, anddineInare consistent (delivery location present whendeliveredByisClient;tableIdandguestspresent forDineIn) - Cancel path tested end-to-end on a sandbox sale
- Delivery status transitions tested (
SALE_DISPATCHED→SALE_DELIVERED) on a sandbox sale - Reconciler runs periodically using the org-wide list endpoint (beta)
- 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 - Re-enable logic in place if a subscription is auto-disabled
Further reading
- Getting Started — OAuth2 credential setup
- Sale webhook events — event schemas, payload structure, and how to subscribe
- Subscribe to Flipdish Events (v3) — webhook delivery contract and signature verification
- User Agents — required
User-Agentformat - Build a POS sales analytics integration — full analytics pipeline: onboarding, menu sync, canonical schema mapping, App Store distribution
- Build a marketplace integration — marketplace-specific guide (menu sync, operations updates, snooze handling)
- API Reference (v3.0) — every v3 endpoint and model
Updated about 18 hours ago

