menu.publication.completed
A menu publication finished, successfully or not.
Delivered to the ONE url you registered for menus. Return any 2xx to acknowledge; anything else is retried with exponential backoff. Check data.stores[] for the per-store, per-platform outcome — the publication reaching a terminal state does not mean every store succeeded.
When it fires. Once for the publication as a whole, never once per store, each time it reaches a terminal state — the moment fetchMenuPublications would first report it completed or failed:
- every store finished, some possibly with a failure —
data.statusiscompleted; - the publication failed as a whole —
failed; - it was cancelled while still
queuedorpublishing—failed, the value the read reports for a cancelled publication. Cancelling a publication that had already failed sends nothing more: itsfailedevent was already sent; - no storefront had a menu to publish —
completed, with an emptydata.stores, sent at once.
A reopened publication sends a second event. When OneTablet retries the failed stores of a finished publication, it goes back to publishing and its completedAt is cleared; when it finishes again, this fires again with the new completedAt and a new id. Both deliveries are real events, not duplicates: keep the one with the later data.completedAt.
The id. Deduplicate on the envelope id, as for every event. For this event it is built from the publication and the instant this terminal state was reached — today <publicationId>:<completedAt>, for example 9b2f…:2026-10-08T12:34:56.123Z — so every retry of one delivery carries the same id, and a reopened publication's next event carries a new one. It is not a UUID: store it as a string, and read data.publicationId and data.completedAt rather than parsing it.
Request
- eventstring
Always
menu.publication.completed.Possible values: [menu.publication.completed] - dataMenuPublication
The publication and its per-store, per-platform outcomes — the same object
fetchMenuPublicationsreturns. - idstring
Unique event id — the deduplication key.
Deliveries are at-least-once and unordered, so you WILL receive the same event more than once. Every copy of one event carries the same
id: retries of a delivery, and the deliveries to each url you have registered. Two legitimate transitions of the same subject are two different events and carry two different ids.Key your idempotency on this field alone. Nothing else in the payload is a dedup key —
timestampis the moment the attempt was SENT and differs between copies of one event. - timestampstring<date-time>
When this delivery was SENT — not when the event happened. Order by the transported state, never by arrival.
- organization_idstring
Your organization id, also carried on the delivery's
X-Organization-IDheader.Snake case on this ONE field is a long-lived wire value; every other field on this API is camelCase.
Treat it as a sanity check, never as proof of origin — it is a value inside a body anyone who learns your url can POST. Proof of origin is the
X-OneTablet-Signatureheader, which store-event deliveries carry on every attempt (ONE-2747); see "Verifying webhook signatures". Phase 0 order deliveries are not signed yet, so for those there is no cryptographic authentication at all.
{
"event": "menu.publication.completed",
"data": {
"publicationId": "00000000-0000-0000-0000-000000000000",
"menuId": "00000000-0000-0000-0000-000000000000",
"status": "queued",
"requestedAt": "<requestedAt>",
"completedAt": "<completedAt>",
"stores": [
{
"storeId": "00000000-0000-0000-0000-000000000000",
"platform": "<platform>",
"status": "queued",
"confirmationMode": "acknowledged",
"platformError": "<platformError>",
"publishedAt": "2026-01-01T00:00:00Z",
"failedAt": "2026-01-01T00:00:00Z"
}
]
},
"id": "00000000-0000-0000-0000-000000000000",
"timestamp": "2026-01-01T00:00:00Z",
"organization_id": "00000000-0000-0000-0000-000000000000"
}About Menus
Menus — two reads (ONE-2726) and one write (ONE-2780), the section's full published surface.
publishMenu is the ONLY WRITE here and the only operation on this whole API that reaches live DoorDash / UberEats / Grubhub storefronts. There is no unpublish. ONE-2724 once held it behind an undocumented header; ONE-2908 removed that, so today the only thing standing in front of it is authentication plus the org scope check below. It is still absent from MANAGEMENT_API_READ_METHODS in ManagementApiKeyHook, on purpose — that list is now the platform's read/write CLASSIFIER (ONE-2890, ONE-2718 build on it), and a write on a read list would be a lie. Do not "fix" that by adding it.
All three operations are org-scoped to the organization the api key resolves to. Cross-tenant and unknown ids are INDISTINGUISHABLE by design — every one of the three returns the published 404 shape for both cases, same convention as StoresApi / ItemAvailabilityApi. On publishMenu that rule has teeth beyond information disclosure: a foreign menuId must never become a write, so the scope check runs BEFORE anything is enqueued.
NOT IMPLEMENTED: replay via Idempotency-Key. The published contract declares the parameter on publishMenu, and nothing on the platform implements idempotent replay today — the same honest gap ItemAvailabilityApi's header records for setItemAvailability (ONE-2621 decision 3). ⚠️ IT MATTERS MORE HERE: a retried publishMenu creates a SECOND publication and re-submits to the platforms rather than returning the first result. That is not corrupting — a publication is a full replace of the same saved state, so the second is a no-op in content — but it doubles platform traffic and produces two publicationIds for one intent. Callers should treat a timeout as "may have succeeded" and check fetchMenuPublications before retrying.