Skip to content

Third-Party Integrations

Overview

Integrations connect Upvendo with external POS systems, online sales channels, and delivery marketplaces. There is no single /integrations page in the back-office — each integration has its own page (for example /square, /mpluskassa, /deliveroo) and connection dialog, reached from the channel/connect dialogs (AddChannelDialog for online channels, AddInhouseChannelDialog for in-house POS). Connecting an in-house channel or marketplace creates a ThirdPartyIntegration record.

Key Purpose: Connect Upvendo with external POS systems and sales/delivery channels.

This page describes the full catalog of integrations. For setup details on any one integration, see its dedicated page (see the table below).

Purpose

Integrations let a merchant use Upvendo's customer-facing channels (Kiosk, Online Ordering, Table QR Ordering) alongside an external POS or marketplace, keeping menus, orders, and (for some providers) inventory in sync. Each connection stores its credentials, settings, and sync status in a ThirdPartyIntegration record scoped to a location (and a merchant).

Payments are separate. Stripe and Viva Wallet are payment providers configured through payment profiles (Settings → Payments), not third-party integration records. See Payments. Square is the exception — it is both a POS integration and a payment provider.

Key Concepts

  • ThirdPartyIntegration record: The MongoDB document (stored on the locations collection) that represents a connected integration. It holds credentials, settings, sync_status, provider, is_active, is_sandbox, last_sync_at, last_successful_test, and external_id. (Source: app/RawModels/ThirdPartyIntegration.php.)
  • AbstractThirdPartyIntegrationService: A base class (app/Services/BackOffice/AbstractThirdPartyIntegrationService.php) that provides enable, disable, update, getStatus, getIntegration, syncTransactions, processWebhook, updateTransactionStatus, and updateItemStatus. Note: not every provider service extends this base class — several (e.g. MplusKassa, the Kassanet providers, Lightspeed K-Series) have their own service architecture.
  • Channel categories: ThirdPartyIntegrationHelper groups providers into online channels (uber_eats, deliveroo, shopify, trivec), location-scoped in-house channels (hendrickx, vanhoutte, shopcaisse), and merchant-scoped in-house channels (square, mpluskassa, lightspeed).
  • One in-house channel per merchant/location: Enabling an in-house channel checks for a conflicting in-house channel; merchant-scoped channels (Square, MplusKassa, Lightspeed) conflict across the whole merchant, and a location already on a location-scoped channel cannot add another. Enabling a provider that already exists for a location throws an error.
  • Order attribution: Orders that come from an external channel are stored as transactions with order_channel (e.g. Uber Eats, Shopify), external_ids (a map of provider → platform order ID), and external_data (a map of provider → original payload). (Source: app/RawModels/Transaction.php.)

Actions

Connect an Integration

Open the relevant connect dialog (online channel via AddChannelDialog, in-house POS via AddInhouseChannelDialog), enter the provider-specific credentials, and connect. In-house connections check prerequisites first (no conflicting in-house channel; a branding profile is required for Hendrickx, Vanhoutte, Shopcaisse, and MplusKassa).

Disconnect an Integration

Disconnect the provider for a location. Provider-specific cleanup runs first, then the integration record is deleted. Disconnect is blocked while a sync is in_progress.

Sync Menu / Catalog

Push or pull the catalog, depending on the provider. Marketplaces (Deliveroo, Uber Eats) and Shopify export the Upvendo menu; import-only POS (Shopcaisse, Kassanet/Hendrickx/Vanhoutte) pull their catalog into Upvendo; Square, MplusKassa, and Lightspeed K-Series sync bidirectionally (Lightspeed pushes item create/edit/images out while categories and modifier groups are pull-only). See each provider's page for direction and triggers.

Monitor Integration Status

The admin Integration Monitoring area (/integration-monitoring, VIEW_INTEGRATION_MONITORING permission) surfaces per-integration status, last sync time, last successful test, and consecutive failures across merchants. (Source: IntegrationMonitoringService.)

Location

  • Back-office pages: per-provider — /square, /mpluskassa, /lightspeed, /hendrickx, /vanhoutte, /shopcaisse, /deliveroo, /uber-eats, /shopify. Admin monitoring at /integration-monitoring.
  • Backend: no single IntegrationController. Each provider has its own controller and route prefix under routes/api/backoffice/ (e.g. square.php, mpluskassa.php, lightspeed.php, kassanet.php, shopcaisse.php, shopify.php, deliveroo.php, uber-eats.php). Hendrickx and Vanhoutte share KassanetIntegrationController via /kassanet/{provider}/{locationId}. Webhooks live under guest routes (routes/api/guest.php).

Integration Catalog

IntegrationCategoryScopePageNotes
SquareIn-house POS (full)Merchant-scoped/squareAlso handles payments; bidirectional menu/order/inventory sync
MplusKassaIn-house POSMerchant-scoped/mpluskassaBidirectional menu sync; requires Stripe/Viva for payments
Lightspeed K-SeriesIn-house POSMerchant-scoped/lightspeedItems push out (create/edit/images); categories + modifier groups pull-only; real-time order push; requires Stripe/Viva for payments
HendrickxIn-house POSLocation-scoped/hendrickxKassanet protocol; requires Stripe/Viva for payments
VanhoutteIn-house POSLocation-scoped/vanhoutteKassanet protocol; requires Stripe/Viva for payments
ShopcaisseIn-house POSLocation-scoped/shopcaisseRead-only catalog pull; requires Stripe/Viva for payments
DeliverooDelivery marketplaceLocation-scoped/deliverooOrders in via webhook; menu pushed out
Uber EatsDelivery marketplaceLocation-scoped/uber-eatsOrders in via webhook; menu pushed out
ShopifyE-commerceLocation-scoped/shopifyBidirectional menu sync; OAuth + webhooks

For full per-integration detail, see the integration docs: Square, MplusKassa, Lightspeed K-Series, Hendrickx, Vanhoutte, Shopcaisse (Kassanet), Deliveroo, Uber Eats, and Shopify.

Not active in the UI: trivec exists as a recognized online-channel slug in the backend (ThirdPartyIntegrationHelper, region restrictions) but is commented out in the connect dialog — it is not a live integration.

Star Micronics — removed, does not exist: There is no Star Micronics integration in any Upvendo production repo. The former OAuth 2.0 / PKCE scaffold (service, Socialite provider, auth controller, star_micronics config block and the commented-out routes/web.php routes) has been deleted from upvendo-backend. A case-insensitive search for micronics across the production backend and back-office returns zero files. Do not describe it as pending, scaffolded or planned. Network printing works through Upvendo's own printer devices: the device is born activated with its own token and polls Upvendo over Epson Server Direct Print (POST /api/print/epson/sdp).


Delivery Marketplace Integrations

Deliveroo

Purpose: Receive Deliveroo orders in Upvendo and push the Upvendo menu to Deliveroo.

  • Scope: Location-scoped (each Deliveroo site maps to one Upvendo location).
  • Credentials (stored on the integration record): include site_id and brand_id. The webhook matches an incoming order to a location by credentials.site_id + credentials.brand_id.
  • Orders: Flow in one-way via webhook (POST /api/.../deliveroo/orders, verify.deliveroo-webhook middleware). A missing site_id/brand_id causes the webhook to be logged and skipped.
  • Menu: One-way push from Upvendo to Deliveroo, triggered manually. Prices can differ from in-store (set on the Upvendo items before syncing).

See the Deliveroo integration page for full details.


Uber Eats

Purpose: Receive Uber Eats orders in Upvendo and push the Upvendo menu to Uber Eats.

  • Scope: Location-scoped (an Uber Eats store maps to one Upvendo location).
  • Auth: Two-step OAuth — a user authorization-code flow plus a client-credentials flow. The user token is stored per vendor; a separate credential record holds the refresh token.
  • Orders: Flow in one-way via webhook (POST /api/.../uber-eats, verify.uber-eats-webhook middleware).
  • Menu: One-way push from Upvendo to Uber Eats.

See the Uber Eats integration page for full details.


In-House POS Integrations

Square (merchant-scoped, full POS)

Purpose: Sync menu, orders, locations, and inventory with Square, and handle payments through Square.

  • Auth: OAuth2 authorization-code flow. Locations are imported automatically.
  • Sync: Bidirectional for menu (categories, items, modifier groups, variant groups), orders, locations, and inventory. A configurable daily auto-sync can run.
  • Payments: Square handles payments directly — when Square is connected, Square Payments replaces Stripe (online ordering) and Viva Wallet (kiosk), so no payment profile is needed.
  • Webhooks: POST /api/.../square (verify.square-webhook).

See the Square integration page for full details.


MplusKassa (merchant-scoped)

Purpose: Connect to MplusKassa POS (common in the Netherlands) with bidirectional menu sync, order forwarding to MplusKassa, and inventory sync from MplusKassa.

  • Scope: Merchant-scoped — one connection imports all MplusKassa branches as Upvendo locations.
  • API: SOAP-based, via the MplusQapi SDK.
  • Payments: Requires a payment profile (Stripe for online ordering, Viva Wallet for kiosk).
  • Webhooks: POST /api/.../mpluskassa/{event} (verify.mpluskassa-webhook).

See the MplusKassa integration page for full details.


Lightspeed K-Series (merchant-scoped)

Purpose: Read menu, items, modifiers, floor plans, orders, payments, and financial data from Lightspeed Restaurant K-Series so Upvendo channels can sell against the Lightspeed catalog. The connection is two-way: Upvendo also pushes orders to the POS in real time and pushes item create/edits (including images) back to the Lightspeed catalog.

  • Auth: OAuth 2.0 authorization-code via Keycloak OIDC as a confidential client (client_id + client_secret, Basic-auth token exchange, refresh-token rotation) — no PKCE, no API keys.
  • Sync: Item catalog is two-way — Upvendo pushes item create/edit and images out to Lightspeed (triggered on item save), while categories/accounting-groups and modifier groups are pull-only (the K-Series Menu API is GET-only). Menu/catalog sync runs once daily (settings.sync_time, defaulting to 07:01); the time is a local wall-clock read in settings.sync_timezone, not UTC. Order pushes are real-time.
  • Payments: Requires a payment profile (Stripe / Viva Wallet).
  • Webhooks: POST /api/.../lightspeed (verify.lightspeed-k-series-webhook).

See the Lightspeed K-Series integration page for full details.


Hendrickx & Vanhoutte (location-scoped, Kassanet)

Purpose: Connect a location to a Hendrickx or Vanhoutte POS register, pull its catalog into Upvendo, and push orders to the register after payment.

  • Backend: Both run on the shared AbstractKassanetService (the Kassanet protocol) and differ only by provider. They share one controller/route (/kassanet/{provider}/{locationId}).
  • Transport: JSON over HTTPS with HMAC-SHA256 signing; optional Blowfish CBC encryption per integration.
  • Payments: Requires a payment profile (Stripe / Viva Wallet).

See the Hendrickx, Vanhoutte, and Kassanet integration pages for full details.


Shopcaisse (location-scoped)

Purpose: Pull the Shopcaisse catalog into Upvendo and push orders back to Shopcaisse after payment.

  • API: REST with JWT (AppToken) authentication.
  • Sync: Catalog is read-only inbound (pulled from Shopcaisse).
  • Payments: Requires a payment profile (Stripe / Viva Wallet).
  • Webhooks: POST /api/.../shopcaisse (verify.shopcaisse-webhook).

See the Shopcaisse integration page for full details.


E-Commerce Integrations

Shopify

Purpose: Sync the menu between Upvendo and a Shopify online store.

  • Scope: Location-scoped.
  • Auth: OAuth 2.0 (a legacy API key/secret method is deprecated).
  • Sync: Bidirectional — Shopify collections ↔ Upvendo display groups, Shopify products ↔ Upvendo items, Shopify variants ↔ Upvendo variant groups.
  • Webhooks: POST /api/.../shopify-webhook (verify.shopify-webhook) for products, collections, inventory, orders, and customers.

See the Shopify integration page for full details.


Payment Providers (not third-party integrations)

Stripe and Viva Wallet are payment providers, configured per payment profile in Settings → Payments — they are not ThirdPartyIntegration records.

  • Stripe: Online-ordering customer payments (per-profile connected account). Also used for platform subscription billing. See Stripe and Payments.
  • Viva Wallet: Kiosk / in-person card-terminal payments (per-profile, ISV model). See Viva Wallet.

When Square POS is connected, Square handles payments and payment profiles are not used.


Business Logic

Integration Status

Two related status layers exist:

Per-integration sync_status.status (set by the base service):
├── pending      Just enabled, awaiting first sync
├── in_progress  Sync running
├── success      Last sync succeeded
└── error        Last sync failed (message stored)

Admin monitoring status (IntegrationMonitoringService):
├── connected
├── disconnected
├── syncing       (sync_status.status == in_progress)
└── error         (derived from connection_status / consecutive_failures)

is_active (boolean) records whether the integration is enabled. There is no separate "Connecting" or "Paused" lifecycle state on the integration record.

Connectivity Failures and Auto-Disconnect Notification

For Square, MplusKassa, Lightspeed K-Series, Hendrickx/Vanhoutte (Kassanet), and Shopcaisse (the providers wired through IntegrationConnectivityTrait), a sync failure is inspected to see whether it's a connectivity failure — the vendor's API being unreachable — as opposed to a data/business error:

  • What counts as a connectivity failure: network-level errors (timeout, connection refused/reset, DNS failure, SSL errors, etc.), plus any vendor HTTP 5xx response (502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout, or any other 500–599 status). 5xx is detected both by the underlying GuzzleHttp\Exception\ServerException type and by matching 5xx wording in the (possibly re-wrapped) exception message, so it's caught even when a provider SDK re-throws the error in its own text (e.g. "Lightspeed K-Series API error (502): ...", "Vanhoutte API server error (HTTP 503): ..."). A queue timeout/max-attempts failure (the job itself running too long) is explicitly excluded — that's treated as an internal job issue, not a vendor disconnect.
  • Effect: on a connectivity failure, sync_status.connection_status.status is set to disconnected (with a redacted failure message and timestamp) — independent of the is_active flag — and settings.consecutive_failures is incremented. This is what the admin Integration Monitoring dashboard shows as "disconnected". The status clears automatically (connected, consecutive_failures reset to 0) the next time a sync or API call to that provider succeeds.
  • Merchant email notification: the first time an integration transitions from connected to disconnected (not on every subsequent failure while it stays disconnected), Upvendo automatically emails the merchant (subject "Upvendo - Integration Connection Issue", via SendGrid) at the merchant's account email, and BCCs the reseller's active admin users. Repeated failures while the integration is already marked disconnected do not trigger repeat emails — only the connected→disconnected transition does. A successful sync/call afterward silently clears the disconnected status; there is no "reconnected" email.
  • Per-user opt-out (BCC recipients only): a reseller/platform user can switch off "Receive integration alert emails" on their user record, which removes them from the BCC list on these notifications. It is an opt-out model — a user with no preference stored, or no value for this key, still receives them (User::wantsNotification(), key integration_disconnected). The merchant's To: recipient is never filtered — a merchant always gets the email about their own integration; only the reseller BCC list honours the switch. The same filter applies on the Kassanet notification path. The switch is edited on the user record in Global Settings, and the Reseller Detail page badges a user who has opted out.
  • Non-connectivity failures (e.g. invalid catalog data, a validation error) still increment consecutive_failures and are recorded on the integration, but they do not flip the connection status to disconnected and do not trigger the email.

Order Attribution

Order received from a channel


Transaction stored with:
├── order_channel  = channel name (e.g. "Uber Eats", "Shopify")
├── external_ids   = { provider: platform order id }
└── external_data  = { provider: original payload }
Push out (Upvendo → platform):  Deliveroo, Uber Eats
Pull in  (platform → Upvendo):  Shopcaisse, Hendrickx/Vanhoutte (Kassanet)
Bidirectional:                  Square, MplusKassa, Shopify,
                                Lightspeed K-Series (items push out; categories +
                                modifier groups pull-only)

Business Rules

  • Enabling a provider that already has an integration record for a location throws an error (no duplicate per location per provider). In-house channels additionally conflict across the merchant/location: Square, MplusKassa, and Lightspeed are merchant-scoped and conflict for the whole merchant; location-scoped channels (Hendrickx, Vanhoutte, Shopcaisse) conflict with each other and with any merchant-scoped in-house channel.
  • Connecting Hendrickx, Vanhoutte, Shopcaisse, or MplusKassa requires the merchant to have at least one branding profile.
  • Transaction sync (on services that use the base syncTransactions) defaults to the last 7 days when no date range is given; it sets sync_status.status to in_progress during the run and success or error on completion.
  • An integration cannot be disconnected while its sync is in_progress.
  • Read-only inbound POS providers (e.g. Shopcaisse) can block catalog create actions in Upvendo via posCatalogSupports, returning a 422 so the catalog stays managed in the POS.
  • When Square POS is connected, Square handles payments, replacing Stripe (online ordering) and Viva Wallet (kiosk).
  • Integration credentials and settings are scoped per location (and per merchant), so multi-location businesses can configure providers per location subject to the in-house conflict rules above.
  • For Square, MplusKassa, Lightspeed K-Series, Hendrickx/Vanhoutte, and Shopcaisse, a vendor connectivity failure (network error or HTTP 5xx) during sync automatically marks the integration "disconnected" and — only on the first such transition, not on repeat failures — emails the merchant and BCCs the reseller's active admins. Reseller users who switched off "Receive integration alert emails" are dropped from that BCC list; the merchant's own recipient is never filtered. The status clears automatically on the next successful sync.

FAQs

  • Is there one "Integrations" page? No. Each integration has its own page (e.g. /square, /mpluskassa, /deliveroo). New connections are made through the channel/connect dialogs. There is a separate admin Integration Monitoring view for cross-merchant health.
  • Can I run more than one in-house POS? No. In-house channels conflict — a merchant can have only one in-house POS active (merchant-scoped providers conflict merchant-wide; location-scoped providers conflict per location). Marketplaces (Deliveroo, Uber Eats) and Shopify are separate channels and can run alongside an in-house POS.
  • Can I connect the same marketplace to multiple locations? Yes. Marketplaces are location-scoped — each location gets its own integration record with its own credentials.
  • Do Stripe and Viva Wallet count as integrations here? No. They are payment providers set up via payment profiles (Settings → Payments). Square is the only provider that is both a POS integration and a payment provider.
  • How do I know if an integration is healthy? Use the admin Integration Monitoring view (last sync time, last successful test, consecutive failures, status), or check the provider's own page.
  • Is Star Micronics printing available? No — there is no Star Micronics integration at all; the old scaffold was removed from the backend. Network receipt printers are set up as printer devices that poll Upvendo over Epson Server Direct Print. Use the standard receipt/printer features.
  • Why did I get an "Upvendo - Integration Connection Issue" email? One of your in-house POS integrations (Square, MplusKassa, Lightspeed K-Series, Hendrickx, Vanhoutte, or Shopcaisse) hit a connectivity failure during sync — either a network error or the vendor returning an HTTP 5xx error — and flipped from connected to disconnected. Upvendo emails the merchant automatically the first time this happens (your reseller's admins are BCC'd); it does not re-send on every subsequent failure while the integration stays disconnected.
  • Do I need to manually reconnect after a vendor outage? No, not for a transient vendor 5xx/network failure on Square, MplusKassa, Lightspeed K-Series, Hendrickx/Vanhoutte, or Shopcaisse — the "disconnected" status clears automatically the next time a sync or API call succeeds. You only need to manually reconnect if credentials expired or the integration was actually disabled/removed.
  • Does a vendor error mark my integration as disconnected even if is_active is still true? Yes. The connectivity/disconnected status (sync_status.connection_status) is tracked separately from is_active — a 5xx or network failure during sync can mark it "disconnected" on the monitoring dashboard while the integration itself stays enabled.

Troubleshooting

Problem: Orders not coming through

Causes:

  1. Integration disconnected (is_active false) or deleted
  2. Invalid or expired credentials
  3. Webhook not reaching Upvendo, or failing signature verification
  4. Platform-side outage
  5. For Square, MplusKassa, Lightspeed K-Series, Hendrickx/Vanhoutte, or Shopcaisse: a vendor 5xx error or network failure during sync flipped the integration to "disconnected" (sync_status.connection_status.status) — the merchant should have received an "Upvendo - Integration Connection Issue" email when this first happened

Solutions:

  1. Reconnect the integration
  2. Re-authenticate / refresh tokens
  3. Verify the webhook URL and that the provider's signature secret matches
  4. Check the platform's status
  5. Check the Integration Monitoring view for the connection status and failure message; the status clears automatically once a sync succeeds again — no manual "reconnect" action is needed for a transient vendor 5xx

Problem: Menu / catalog not syncing

Causes:

  1. Sync was not triggered (most catalog syncs are manual)
  2. API errors or rate limits
  3. Invalid item data
  4. Wrong sync direction expectation (e.g. expecting a push on a pull-only provider)

Solutions:

  1. Trigger the sync from the provider page
  2. Retry; check the sync status message
  3. Validate the item/catalog data
  4. Confirm the provider's sync direction (see the catalog above)

Problem: Sync stuck or status shows error

Causes:

  1. A sync left in in_progress
  2. Repeated failures (raising consecutive_failures)
  3. Authentication expired

Solutions:

  1. Wait for the in-progress sync to finish (disconnect is blocked until then)
  2. Check the Integration Monitoring view for the failure message
  3. Re-authenticate the provider

Notes for Emily

  • There is no generic /integrations page or IntegrationController; route users to the specific provider page.
  • Only one in-house POS can be active per merchant — surface the conflict rule before suggesting a second.
  • Treat Stripe/Viva Wallet as payment-profile setup, not as integrations to "connect" here (except Square, which is both).
  • There is no Star Micronics integration — do not offer to set it up, and do not describe it as scaffolding or "coming soon". Point merchants at printer devices instead — but note the URL + token is revealed only through the permission-gated pairing flow, and no back-office screen shows it yet.
  • Provider-specific behaviour (exact field names, sync internals, payment flows) is documented on each integration's own page; defer there rather than guessing.
  • If a merchant mentions an "Integration Connection Issue" email, or asks why an integration shows disconnected, explain the auto-disconnect/auto-recover behavior above rather than telling them to manually reconnect — it only applies to Square, MplusKassa, Lightspeed K-Series, Hendrickx, Vanhoutte, and Shopcaisse (not the webhook-driven Deliveroo/Uber Eats/Shopify channels), and it recovers on its own once the vendor is reachable again.