Skip to content

Uber Eats Integration

Overview

The Uber Eats integration connects an Upvendo location to an Uber Eats store so that your menu is pushed out to Uber Eats and incoming Uber Eats orders are received automatically into Upvendo.

Key Purpose: Push your menu to Uber Eats and receive Uber Eats orders inside Upvendo.

Purpose

This page lets you connect a single Upvendo location to an Uber Eats store. You authorize Upvendo through Uber's OAuth flow, pick the store to link, then push selected Upvendo menus to Uber Eats and receive Uber Eats orders automatically via webhooks. Payments are handled entirely by Uber Eats.

Key Concepts

  • OAuth Authorization (per merchant): Linking a store requires an Uber OAuth authorization code flow with the eats.pos_provisioning scope. The merchant logs in to Uber and authorizes Upvendo; the resulting user access token and refresh token are stored on the integration record and used to read the store list and to link/unlink the store. (A separate client credentials token, shared across the app, is used for order and menu API calls — see the integration setup doc.)
  • Store Linking (POS provisioning): After OAuth, you select a store from your Uber account and link it. Upvendo calls Uber's POS provisioning endpoint (pos_data) to register Upvendo as the order manager (is_order_manager: true), set require_manual_acceptance: false, and enable order-release, scheduled-order and delivery-status webhooks. (Verified: app/Services/BackOffice/UberEatsService.php lines 640-663.)
  • Location-Scoped: The integration is scoped to one Upvendo location. Each location links its own Uber Eats store. The OAuth tokens, however, are stored on the per-location integration record (there is no shared vendor-level credential store).
  • Webhook-Driven Orders: Uber Eats sends webhooks to Upvendo. UberEatsWebhookService resolves the matching integration in three steps — first by integrator/webhook identifier, then by Upvendo location ID, then by Uber store ID — and dispatches a background job per event type. New orders are auto-accepted once the local transaction is safely stored. (Verified: app/Services/ThirdParty/UberEatsWebhookService.php lines 125-185.)
  • Menu Sync (push-out): The syncMenu action transforms selected Upvendo menus into Uber Eats' format (categories, items, modifier groups with quantity constraints, prices, tax rates, allergen classifications, and availability schedules) and pushes them with a single PUT request to Uber's Menu API. You trigger it from the integration page — and once menus have been synced, editing one of those menus in Upvendo re-pushes the whole set automatically in the background. (Verified: app/Services/BackOffice/UberEatsService.php lines 217-286 and transformMenuForUberEats lines 292-507; auto re-sync in app/Listeners/SyncMenuWithUberEats.php lines 29-42, fired from app/Services/BackOffice/MenuService.php line 127.)
  • Sync Status: Each integration record tracks a sync status used during menu sync. The stored status enum (ThirdPartySyncStatus) has three values: standby, syncing, and error, each with a timestamp and message. (Verified: app/Enums/ThirdPartySyncStatus.php.)
  • Connection Status: Separately from sync status, the status endpoint reports a connection state derived from OAuth and store-selection state: not_connected, pending_store_selection, connected, or provisioning_failed. If a stored settings.connection_status exists it is reported instead of provisioning_failed — for example deprovisioned after Uber deprovisions the store. (Verified: app/RawModels/UberEatsIntegration.php lines 97-111; deprovisioned written in app/Services/ThirdParty/UberEatsWebhookService.php lines 81-93.)
  • Capabilities: Which Uber features a location can use depends on the Uber scopes approved for it. The status response returns a capabilities map (store_read, store_write, menu, orders_read, order_actions, promotions, reports, merchant_delivery), each available true/false with a reason when unavailable. Calls into an unavailable capability are rejected. (Verified: app/RawModels/UberEatsIntegration.php lines 157-185; enforcement in app/Services/UberEats/UberEatsMarketplaceService.php lines 266-275.)

Actions

Connect Uber Eats Account

Click Connect to Uber Eats. Upvendo creates an integration record and returns an Uber OAuth URL (scope eats.pos_provisioning); the browser is redirected to it. After you authorize on Uber, Uber redirects back to Upvendo's callback, which exchanges the authorization code for an access token and refresh token and stores them on the integration record.

After OAuth completes, pick a store from the Select Store dropdown (populated from your Uber account) and confirm. Upvendo saves the store ID while leaving the integration inactive, calls Uber's POS provisioning endpoint to link the store and enable webhooks, and only then marks the integration active with connection_status: connected. If provisioning fails, the store stays saved but the integration stays inactive, so you can retry the selection without reconnecting. (Verified: app/Services/BackOffice/UberEatsService.php lines 166-196.)

Sync Menu to Uber Eats

Select one or more Upvendo menus and push them to Uber Eats. The sync sends categories, items, modifier groups (with quantity constraints), prices, tax rates, allergen classifications, and availability schedules, and replaces the menu on Uber Eats. After the first sync, editing any of those menus in Upvendo re-pushes the set automatically in the background — so the button is mainly for the first push, for changing which menus are synced, and for forcing a re-push.

Disconnect Uber Eats

Disconnecting calls Uber's pos_data DELETE endpoint to unlink the store (best-effort — failures are logged and ignored) and then deletes the integration record. You will stop receiving orders for that location. (Verified: app/Services/BackOffice/UberEatsService.php lines 671-699.)

The disconnect endpoint (DELETE /back-office/uber-eats/{locationId}) exists on the backend, but the Uber Eats page in the back office does not currently render a disconnect button — the page shows only Connect, Select Store, and Sync Menu. (Verified: upvendo-backoffice/src/views/uber-eats/index.vue lines 1-201; the disableUberEats action in src/store/modules/uberEats.ts lines 95-102 has no caller.)

Location

  • Backoffice Route: /uber-eats (Uber Eats integration page). (Verified: file-based route from upvendo-backoffice/src/pages/uber-eats/index.vue.)
  • Backend Controller: app/Http/Controllers/Api/BackOffice/UberEatsIntegrationController.php
  • Backend Services: app/Services/BackOffice/UberEatsService.php (connect / store link / menu sync), app/Services/UberEats/UberEatsMarketplaceService.php (settings, store, remote menu, orders, promotions, reports), app/Services/UberEats/UberEatsTransactionService.php (incoming-order mapping)
  • Vue Component: src/views/uber-eats/index.vue

Fields

There are no credential-entry fields. Uber OAuth credentials are platform-level (configured by the Upvendo team as environment variables), so the merchant never enters a client ID, client secret, or store ID by hand. On the Uber Eats page itself, the back office renders only a store dropdown (after OAuth) and a menu multi-select with a Sync Menu button — no enabled, auto-accept, availability-sync, prep-time, or sandbox control is drawn on that page. (Verified: upvendo-backoffice/src/views/uber-eats/index.vue lines 1-201.)

Behind that page the backend exposes a much wider marketplace API for the same location — integration settings (PUT .../settings), store details/status/prep-time/fulfillment, remote-menu read and per-item update, order list/detail/actions, promotions, and reports. These are live production endpoints; they are simply not wired into the Uber Eats page yet, so treat them as API capabilities rather than on-screen controls. (Verified: routes/api/backoffice/uber-eats.php lines 7-44; the back-office store module calls only status, connect, store-list, select-store, sync-menu, and disconnect — upvendo-backoffice/src/store/modules/uberEats.ts lines 47-102.)

Order Send Timing (integration settings)

PUT /back-office/uber-eats/{locationId}/settings stores two merchant-facing settings that control when an accepted Uber order is pushed to the in-house POS:

Field IDTypeValuesDefault
pos_send_timingstringlocation_default, immediate, scheduledlocation_default
pos_lead_minutesinteger0-1440 (used only with scheduled)0

location_default follows the location's online-ordering rules; immediate sends as soon as the order is accepted; scheduled sends pos_lead_minutes before the order's pickup/delivery time. Both fields are required on that request. (Verified: app/Http/Requests/BackOffice/UberEatsIntegration/UpdateUberEatsSettingsRequest.php lines 13-16; defaults in app/RawModels/UberEatsIntegration.php lines 114-117; behaviour in app/Services/UberEats/UberEatsTransactionService.php lines 685-739.)

Store

PropertyValue
Field IDstore_id
LabelSelect Store
TypeDropdown (populated from the Uber store list after OAuth)
RequiredYes (to link a store)

Description: The Uber Eats store to link to this location. Chosen from the dropdown of stores fetched from your authorized Uber account, not typed in.


PropertyValue
Field IDmenu_ids
LabelSelect Menus
TypeMulti-select (Upvendo menu IDs)
RequiredYes (for Sync Menu)

Description: The Upvendo menus to push to Uber Eats when you click Sync Menu. Multiple menus can be selected and are combined into one payload.


Business Logic

Order Flow

Customer orders on Uber Eats
        |
        v
New-order webhook received (orders.notification / orders.release / ...)
        |
        v
ProcessUberEatsOrderNotificationJob (queued)
        |
        v
Full order fetched from Uber API
        |
        v
Transaction + KDS items created in Upvendo
        |
        v
Order auto-accepted on Uber (only after the local order is stored)
        |
        v
sent_at calculated -> order forwarded to in-house POS (if configured)
        |
        v
Kitchen prepares -> driver picks up

(Verified: app/Jobs/UberEats/ProcessUberEatsOrderNotificationJob.php lines 96-133.)

Select menus in Upvendo
        |
        v
Click Sync Menu   -- or edit an already-synced menu (auto re-sync)
        |
        v
Convert to Uber Eats format (categories, items, modifier groups, schedules)
        |
        v
PUT to Uber Eats Menu API
        |
        v
sync_status updated on the integration record

Order Status

Uber Eats orders are received already paid (Uber handles payment). On a new order, the transaction is stored with order_status: Queued and status: Complete, and Upvendo calls Uber's accept endpoint to auto-accept. Cancellations from Uber set the transaction order_status to Cancelled and mark its items done. Upvendo does not push a custom Confirmed/Preparing/Ready status sequence back to Uber Eats. (Verified: app/Services/UberEats/UberEatsTransactionService.php lines 114-165; app/Jobs/UberEats/ProcessUberEatsCancelNotificationJob.php.)

If an incoming order cannot be mapped locally — an unknown item, modifier group or modifier, a missing order ID, an empty cart, an invalid total, or free-text item instructions — Upvendo denies the order on Uber with reason type ITEM_ISSUE and code INVALID_ORDER instead of accepting it, and logs the reason. No transaction is created. (Verified: app/Jobs/UberEats/ProcessUberEatsOrderNotificationJob.php lines 101-117; validation in app/Services/UberEats/UberEatsTransactionService.php lines 653-677 and lines 318-441.)


Business Rules

  • Connecting starts an OAuth authorization-code flow (scope eats.pos_provisioning); the store is linked only in the separate Select Store step, which calls Uber's POS provisioning (pos_data) endpoint with is_order_manager: true, require_manual_acceptance: false, integrator_brand_id: upvendo_brand, integrator_store_id set to the Upvendo integration record ID, merchant_store_id: upvendo_{locationId}, and order-release, scheduled-order and delivery-status webhooks all enabled (webhooks_version: 1.0). (Verified: app/Services/BackOffice/UberEatsService.php lines 631-669.)
  • Disconnecting calls Uber's pos_data DELETE endpoint to unlink the store (best-effort) and then deletes the local integration record, so the marketplace stops sending orders.
  • Menu sync converts prices to the smallest currency unit and applies each item's/modifier group's delivery tax rate. Items also carry their Upvendo allergens as dish_info.classifications. The payload sets disable_item_instructions: true (customers cannot add free-text item instructions). (Verified: app/Services/BackOffice/UberEatsService.php lines 443-506.)
  • Webhook events are matched to an integration in three steps, in order: (1) the integrator/webhook identifier — meta.integrator_store_id, a top-level integrator_store_id, or a partner identifier of type INTEGRATOR_STORE_ID — matched against settings.webhook_identifier and then against the integration _id; (2) the Upvendo location, from meta.location_id or from merchant_store_id / partner_store_id with the upvendo_ prefix stripped; (3) the Uber store ID (meta.user_id, meta.store_id or a top-level store_id) matched against credentials.store_id, preferring an active integration. Payloads with none of these identifiers, and payloads that match no integration, are logged and dropped. (Verified: app/Services/ThirdParty/UberEatsWebhookService.php lines 125-253.)
  • Webhook delivery is acknowledged immediately and processed on the webhooks queue by ProcessUberEatsWebhookJob (4 tries, backoff 1/5/30/120s), which de-duplicates on the Uber event ID (event_id or webhook_meta.webhook_msg_uuid) so a replayed webhook is not processed twice. Order events are then dispatched to ProcessUberEatsOrderNotificationJob, ProcessUberEatsScheduledNotificationJob, or ProcessUberEatsCancelNotificationJob, each with a unique lock keyed on the Uber order ID (meta.resource_id). (Verified: app/Jobs/UberEats/ProcessUberEatsWebhookJob.php lines 13-58.)
  • New orders are auto-accepted for the merchant: require_manual_acceptance: false at provisioning, and the order job calls Uber's accept endpoint once the transaction and KDS rows are stored. Orders that fail local validation are auto-denied instead. There is no manual-accept toggle on the Uber Eats page, but the backend does expose per-order actions (accept, deny, cancel, ready, ready-time, adjust-price, validate-fulfillment, resolve-fulfillment, replacement-recommendations, courier-count, merchant-delivery-status) via POST /back-office/uber-eats/{locationId}/orders/{orderId}/{action}. (Verified: routes/api/backoffice/uber-eats.php lines 32-33.)
  • If Uber sends a store.deprovisioned event, Upvendo sets the integration is_active = false and settings.connection_status = deprovisioned. The record is kept, so reselecting the store restores it. (Verified: app/Services/ThirdParty/UberEatsWebhookService.php lines 81-93.)
  • Store, menu, order, promotion and report calls are gated by the location's approved Uber scopes and return a 409 when the scope is missing. (Verified: app/Services/UberEats/UberEatsMarketplaceService.php lines 266-275.)
  • Payment is external: Uber Eats handles all customer payments and you receive payouts from Uber directly. No payment profile is needed in Upvendo.

FAQs

  • Do I need to enter Uber Eats API credentials? No. Upvendo uses platform-level OAuth credentials configured server-side. You authorize via Uber's login flow; Upvendo stores your user access and refresh tokens on the integration. You never type a client ID, secret, or store ID.
  • Can I set different prices for Uber Eats? Yes — items support an Uber Eats channel price. Incoming orders are priced using the Uber Eats channel price (getPrice('Uber Eats')) of the matched Upvendo item, and menu sync pushes item prices to Uber. Set the Uber Eats price on items if it should differ from the default.
  • What happens when an order is cancelled on Uber Eats? Uber sends an orders.cancel webhook, which triggers a background job that sets the transaction's order status to Cancelled and marks its items done. (Nothing is deleted.)
  • How are modifier quantity limits handled? Modifier group settings (mandatory, allow-duplicates, max-selected) are translated into Uber Eats' quantity_info format with min_permitted / max_permitted values, using per-modifier overrides (context MODIFIER_GROUP) and group-level quantity where applicable.
  • Can I connect multiple stores? Each Upvendo location links one Uber Eats store. The store list is fetched with pagination, and you can link different locations to different stores; each location runs its own OAuth + store-selection.
  • My Uber Eats integration turned itself off — why? Uber can deprovision a store (for example when the store is removed or the POS link is revoked from Uber's side). Upvendo receives a store.deprovisioned webhook and deactivates the integration, setting connection_status to deprovisioned. Nothing is deleted — reselect the store on the Uber Eats page to re-provision and reactivate. (Verified: app/Services/ThirdParty/UberEatsWebhookService.php lines 81-93.)
  • Why did an Uber order get rejected automatically? Upvendo denies an incoming order (reason type ITEM_ISSUE, code INVALID_ORDER) when it cannot be mapped locally — most often an item, modifier group or modifier on the Uber menu that no longer exists in Upvendo, or an order carrying free-text item instructions. Re-sync the menu so Uber's copy matches Upvendo. (Verified: app/Jobs/UberEats/ProcessUberEatsOrderNotificationJob.php lines 101-117.)
  • Do menu edits push to Uber Eats automatically? Only for menus you have already synced. Editing a menu whose ID is in the integration's menu_ids re-pushes the whole synced set in the background; a menu that has never been synced still needs the Sync Menu button. (Verified: app/Listeners/SyncMenuWithUberEats.php lines 29-42.)
  • When is an Uber order sent to my POS? By default it follows the location's online-ordering rules. The integration settings can override this with pos_send_timing = immediate (send on acceptance) or scheduled with pos_lead_minutes (send that many minutes before the pickup/delivery time). (Verified: app/Services/UberEats/UberEatsTransactionService.php lines 685-739.)

Troubleshooting

Problem: Orders not coming through

Causes:

  1. Integration not connected / store not selected (connection status is not_connected or pending_store_selection)
  2. Store selected but provisioning never completed (provisioning_failed, integration still inactive)
  3. OAuth tokens expired or store not linked on Uber's side
  4. Uber deprovisioned the store (connection_status: deprovisioned, integration inactive)
  5. Restaurant offline in Uber Eats Manager

Solutions:

  1. Connect and complete store selection (provisioning enables webhooks)
  2. Re-select the store — the record stays saved and inactive on a failed provision, so a retry is safe
  3. Disconnect and reconnect to refresh OAuth and re-link the store
  4. Re-select the store to re-provision after a deprovision
  5. Check the Uber Eats dashboard and the Activity Log for errors

Problem: Integration switched itself off

Causes:

  1. Uber sent a store.deprovisioned webhook for the linked store (store removed, or the POS link revoked in Uber Eats Manager)
  2. Store selection was made but Uber's provisioning call failed, so the record was never activated

Solutions:

  1. Check the connection status: deprovisioned means Uber ended the link, provisioning_failed means Upvendo never completed it
  2. Re-select the store on the Uber Eats page to re-provision and reactivate — the integration record and its synced menu_ids are preserved either way
  3. If the store no longer exists in Uber Eats Manager, recreate/relist it on Uber first

(Verified: app/Services/ThirdParty/UberEatsWebhookService.php lines 81-93; app/RawModels/UberEatsIntegration.php lines 100-111.)


Problem: An Uber Eats action returns "This Uber Eats capability is unavailable"

Causes:

  1. The location's approved Uber scopes do not cover the feature (reason: scope_not_approved)
  2. Merchant-managed delivery is not switched on for the location (reason: merchant_delivery_not_enabled)
  3. The capability is not present in the status response at all (reason: capability_unknown)

Solutions:

  1. Check the capabilities map in the integration status response to see which features are available
  2. Promotions (eats.store.promotions) and reports (eats.report) are outside the default scope set, so they need the extra scopes approved on the Uber app before they will work
  3. Merchant-delivery status updates additionally require settings.merchant_delivery_enabled

(Verified: app/Services/UberEats/UberEatsMarketplaceService.php lines 266-275; app/RawModels/UberEatsIntegration.php lines 157-185; default scopes in config/services.php lines 192-195.)


Problem: Menu sync fails

Causes:

  1. No store linked / invalid store
  2. Items missing names, prices, or tax rates
  3. Invalid modifier quantity constraints rejected by Uber

Solutions:

  1. Re-select and link the store
  2. Fix item data and re-sync
  3. Review modifier group settings, then re-sync

Examples

Connect and sync (typical flow)

1. Open the Uber Eats page (`/uber-eats`, listed under the Online channels in the sidebar) and click "Connect to Uber Eats"
2. Authorize Upvendo on Uber (OAuth)
3. Back in Upvendo, pick your store from the dropdown and confirm
4. Select the menus to push and click "Sync Menu"

Select Store request body

store_name is optional; when supplied it is stored as settings.selected_store_name and shown as the connected store. (Verified: app/Http/Requests/BackOffice/UberEatsIntegration/SelectUberEatsStoreRequest.php lines 17-21.)

json
{
  "store_id": "store-uuid-1",
  "store_name": "Main Street Kitchen"
}

Sync Menu request body

json
{
  "menu_ids": ["menu-id-1", "menu-id-2"]
}

Update settings request body

json
{
  "pos_send_timing": "scheduled",
  "pos_lead_minutes": 20
}