Appearance
MplusKassa Integration Setup
Looking for guided setup? Start Emily in the backoffice for step-by-step guided setup.
Purpose
The MplusKassa integration connects Upvendo to MplusKassa POS systems commonly used in the Netherlands and Belgium. MplusKassa owns the catalog: products, articles, modifiers, prices, VAT and stock are synced one way, from MplusKassa into Upvendo (read-only), and the synced fields are locked against editing in Upvendo. Orders placed on Upvendo channels (Kiosk, Online Ordering, Table QR) are forwarded one way, from Upvendo to MplusKassa. MplusKassa can also drive payments through Upvendo via a webhook-based external payment flow, and can push completed/cancelled POS sessions back to Upvendo as transactions. This lets the restaurant keep MplusKassa as the central POS while accepting orders through Upvendo.
Key Concepts
Merchant-Scoped Integration
Unlike Hendrickx or Vanhoutte (which are location-specific), MplusKassa is merchant-scoped -- one connection covers all branches. When you connect MplusKassa with your credentials, Upvendo imports your MplusKassa branches as Upvendo locations.
MplusKassa Owns the Catalog (Read-Only Sync)
The menu sync is one-directional: MplusKassa -> Upvendo. Upvendo does not push catalog changes back to MplusKassa during normal operation. To prevent drift, Upvendo locks the fields that come from MplusKassa so they cannot be edited in the back office:
- Items:
name,description,price,plu,modifier_group_ids,allergens,dietary_preferencesare locked. - Categories:
name,descriptionare locked.
Fields that do not come from MplusKassa (for example item images, availability/visibility, channel settings) remain editable in Upvendo.
The KB previously described a "bidirectional" menu sync where Upvendo pushed categories, items, combos and prices back to MplusKassa. That is not how the production integration behaves. The MplusKassa write APIs exist in the codebase but are exposed only on a developer/test route (not reachable in production), and the bulk push-back is an unimplemented TODO. The only narrow exception is single modifier-group edits, which can push preparation-method / article-alteration groups back. Even there, an article-alteration group can only be updated in place -- it must already exist in MplusKassa (an edit is matched by its stored MplusKassa group number). A modifier group newly created in Upvendo is only ever pushed as a preparation-method group; article-alteration groups have to be created in MplusKassa.
SOAP API via MplusQapi SDK
MplusKassa exposes a SOAP-based API accessed through the MplusQapi SDK client. The integration talks to the merchant's MplusKassa API server (default https://api.mpluskassa.nl:47254) for all data operations. The code is organised into a large back-office orchestration service plus a set of per-domain sub-services (products, articles, orders, stock, tables, relations, gift cards, vouchers, media, external payments, etc.).
One-Way Menu Pull
The pull imports, from MplusKassa into Upvendo:
| MplusKassa | Upvendo | Notes |
|---|---|---|
| Product | Category | A Product groups its Articles |
| Article | Item | The actual sellable item |
| PreparationMethodGroup | ModifierGroup | Pull only |
| ArticleAlterationsGroup | ModifierGroup | Pull only |
| Branch | Location | Auto-imported on connect/sync |
bestelbeperking (per article) | order_quantities on the item | Pull only; MplusKassa returns it as a braced array literal, e.g. {4,6,8} |
| VAT code (per article) | Tax rate | Auto-created as MPLUS_VAT_{code} (or MPLUS_VAT_{code}_{altCode} when the article's alternative/takeaway VAT code differs), tax method inclusive |
Stock (amountFree) | Inventory | Pull only |
It also imports product images, allergen and dietary data, and branch-specific price deviations (if an article has a different price for a branch, that branch's price is used for that location).
Modifier Groups and Composite External IDs
Both preparation method groups and article alteration groups import as Upvendo modifier groups. To keep the two MplusKassa group types distinct, the modifier group's external ID is stored as a composite string of the form {type}_{number}, e.g. preparation_method_5 or article_alteration_10. The numeric group number is extracted with a dedicated helper (extractGroupNumberFromCompositeId) -- the composite ID is never cast directly to an integer. Modifier-group sync is pull-only in the bulk sync.
Sync Markers (Incremental Sync)
Instead of fetching everything every time, MplusKassa uses sync markers. A full sync starts at marker 1 (retrieves all products, paged ~1000 at a time); incremental syncs send the last stored marker and only receive changes. The latest marker is stored on the integration record and reused on the next run.
Most scheduled runs are incremental, but the scheduler also performs a daily reconciliation: the first time in-house:auto-sync picks up a MplusKassa integration on a given calendar day it resets the stored product sync marker to 0, so that run starts from marker 1 -- a full catalog re-pull that also triggers orphan detection. Later runs the same day resume incrementally from the stored marker. A manually-triggered Sync Now (see Actions) can force a full sync at any time.
Streaming / Batched Sync for Large Catalogs
To avoid memory exhaustion on large catalogs, products are streamed in batches (generator-based, small chunks), and the sync is split across chained queue jobs (paged product jobs, batch jobs, image dispatch/batch jobs, and a finalization job).
Scheduled Sync
A daily sync time can be configured (whole or half hour, e.g. 03:30). The in-house:auto-sync scheduler task runs every 30 minutes, triggers each integration's sync when its configured time is due, and performs the daily marker-reset reconciliation described above as part of the same run. There is no separate nightly reconciliation task.
If no sync time is configured, MplusKassa integrations still sync daily at a 03:30 default -- unlike most providers, leaving the sync time blank does not disable the daily sync. That default is a local wall-clock read in the integration's sync_timezone, not UTC.
Webhook Event Handling
MplusKassa sends webhooks to Upvendo at /webhook/mpluskassa/{event}. Requests are authenticated with an HMAC-SHA256 signature (X-Mplus-Signature) verified against the integration's webhook secret, keyed by X-Mplus-Subscription-Id. Events are routed through MplusKassaWebhookOrchestrator to handlers in WebhookService:
| Event | Action |
|---|---|
completeSession | Creates a complete, POS-channel transaction in Upvendo (processed after the response) |
cancelSession | Cancels the matching Upvendo transaction (by session ID) |
selectRelation | Links a POS-selected customer to an existing Upvendo customer record (by email/phone) |
backgroundPoll | POS heartbeat -- stamps last_pos_seen_at on the integration for support diagnostics |
startExternalPayment | Creates a pending_payment transaction and tells the POS to keep polling |
pollExternalPayment | Returns current payment status to the POS (see External Payment Flow) |
requestCancelExternalPayment | Marks the payment cancellation_requested |
cancelExternalPayment | Cancels the external payment |
startSession, addSessionLine, removeSessionLine | Acknowledged (no action) |
updateSessionLine | Acknowledged with forensic logging |
creditSession | Acknowledged only (refund processing not implemented) |
The completeSession transaction is created from the POS session lines; it currently records POS customer name/email/phone but does not link an Upvendo customer record, accrue loyalty, or split gift-card tenders.
External Payment Flow
For payments MplusKassa wants Upvendo to drive, the POS uses a poll-based external payment flow:
- startExternalPayment -- Upvendo creates a
pending_paymenttransaction and responds withpolling.finished = falseso the POS keeps polling. - pollExternalPayment -- the POS polls. Upvendo responds with boolean flags rather than a status string:
- still pending: a "waiting" message with
polling.finished = false - succeeded:
confirmed = true,polling.finished = true - cancelled/failed/not-found:
cancelled = true,polling.finished = true
- still pending: a "waiting" message with
- The payment itself is processed elsewhere in Upvendo; the webhook layer is provider-agnostic and is only told the result (it does not reference Stripe or Viva Wallet directly).
- Once Upvendo marks the payment confirmed, the next poll returns
confirmed/finishedand the POS completes the sale.
Order Forwarding (Upvendo -> MplusKassa)
When an Upvendo order reaches a syncable status (complete, or awaiting_invoice for deferred-invoice orders), ProcessOrderJob forwards it to MplusKassa. There is no manual-acceptance hold; forwarding is automatic. On failure the order is flagged and a retry is scheduled.
The forwarding flow depends on the channel/order type:
| Type | MplusKassa flow | Use case |
|---|---|---|
| Regular order (takeaway/delivery) | createOrder -> payOrder (and optionally deliverOrder) | Pickup, delivery |
| Atomic regular order | createOrderV3 (create + pay + prepay in one call) -> optional deliverOrder | Some channels |
| Dine-in with table number | createAndPayTableOrder | Dine-in, pay now |
| Table order (save) | saveTableOrder / payTableOrder | Dine-in workflows |
deliverOrder is only called when the integration's use_deliver_order setting is enabled (it makes the order appear in the active POS workflow for printing). Orders are sent with extOrderId = the Upvendo order number (used as the idempotency / recovery key) and vatMethod = INCLUSIVE. A relation number is required by MplusKassa for order creation -- the integration resolves or creates one (anonymous fallback when there is no linked customer).
Per-Person Lines in the POS Payload
A per-person (bestelbeperking) line is forwarded as one article for a party of N, not N articles:
- The POS line
quantityis the dish count -- the stored article units divided by the party size (the shared ruleApp\Support\PerPersonLine::dishQuantity). - The line
priceis the unit price incl. VAT x the party size, and on this path it is always sent. Normally Upvendo sends no price at all and lets MplusKassa price the article from its own catalogue; a collapsed line has to carry the price explicitly, because that is the only way one article can still carry N x the money. - That unit price comes from the order snapshot's
price(what the guest was actually charged, including any relation-specific override), falling back to the catalogueexternal_data.mpluskassa.priceIncl. - Preparations on a collapsed line carry the party size in their own price too: text-only preparations (
articleNumber0) and article-based modifiers alike are sent atpriceIncl x N. Article-based modifiers are normally priceless unless the repair-resyncsendPricesflag is on -- on a collapsed line they are priced regardless, or MplusKassa charges them once and the order total lands under what the guest paid. The exception is modifiers whose group type isMENU: they never carry a price on any path, because their cost sits inside the article's fixed menu price. - The party size rides along as a text preparation line (
articleNumber = 0, textAantal personen: N) -- the same articleNumber-0 pattern used for per-item kitchen notes, because MplusKassa sales lines have no remark field. It is added wheneverpersons >= 1, so a party of 1 still shows it even though such a line is never collapsed. - Safety fallback: if no usable price can be resolved, or the article units are not a whole multiple of the party size, the collapse is abandoned and the uncollapsed N x line is sent with no folded price. A busier ticket is fine; a wrong total is not.
Relations (POS Customers)
MplusKassa "relations" are POS customer records. Upvendo can sync relations incrementally (sync-marker based), link an Upvendo customer to an existing relation, and create a new relation from an Upvendo customer (only on explicit user action). Relations can carry custom pricing (price group, article discounts, relation discount), which Upvendo can check per relation.
Relation-Aware Pricing (determinePricing)
For relations that have custom pricing, Upvendo can ask MplusKassa to compute exact prices via the beta determinePricing call so the storefront/checkout shows the correct amount. This call has a circuit breaker: if it fails or times out, a per-location cooldown (60 seconds) is opened and subsequent quotes return a "cooldown" fallback instead of hitting MplusKassa. A forced quote (the checkout pricing gate) bypasses an active cooldown so checkout can still get exact pricing.
Per-Person Order Quantities (bestelbeperking)
MplusKassa's per-article bestelbeperking (the POS "Voor hoeveel personen" whitelist) is pulled during the product sync and stored on the item as external_data.mpluskassa.order_quantities. It comes from the MplusKassa getOverview call (fields nr + bestelbeperking) -- it is not carried by the product stream -- and is fetched once per sync run. If that fetch fails it is swallowed: the product sync still completes and the affected items simply come back unrestricted.
Both Kiosk and Online Ordering item payloads expose it as order_quantities, a list of integers. An empty list means unrestricted (clients always get an array, never null) and the item behaves exactly as before.
When the list is not empty the item is treated as per-person:
- The allowed values become a party-size picker shown alongside the normal +/- quantity stepper (Kiosk and Online Ordering both) -- it does not replace the stepper. The picker sets
persons(how many people the dish is for); the stepper sets how many of the dish. - The cart line stores
quantity= dishes x persons -- article units, which is what Upvendo prices and reserves stock against -- pluspersonsalongside it. - Opening the item defaults the quantity to
1and the party size to the first allowed value. So a guest can order 2x a dish for 6 (12 article units), and a dish for 3 and the same dish for 2 stay two separate lines (party size is part of the bag line's identity). - In the bag the row counts dishes (
quantity / persons) with the party size named underneath ("2x Gourmetschotel / Voor 6 personen"). The +/- stepper moves a whole party at a time (it sendsquantity ± persons); on those lines the quantity is plain text, not an input, so a partial party can't be typed in; "-" at one dish removes the line. There is no longer any cap tied to the allowed-values list -- "+" is limited only by stock /max_order_limit(Online Ordering disables the button; Kiosk shows a "max order limit reached" toast). The party size cannot be changed from the bag at all -- there is no edit action on the bag row, so the guest removes the line and adds the item again. - The backend validates the party size against the allowed set, not the line quantity:
items.*.persons(nullable|integer|min:1) on both the Kiosk and Online Ordering create-intent payloads. A request that omitspersons(an older client) is still validated onquantity, so un-upgraded kiosks keep working. A party size outside the set is rejected with HTTP 422 and a message naming the item and its allowed values -- "... can only be ordered in these quantities: 2, 4, 6." - That guard builds a map keyed by item id, so when the same item appears on several lines with different party sizes only the last line's
personsis checked.
This is MplusKassa-only -- no other integration and no back-office form populates order_quantities. Change the allowed set in MplusKassa and re-sync.
Label caveat: Kiosk translates both per-person labels in all seven locales (English: "For how many people" / "For {count} people"). Online Ordering does not: the picker heading (
itemDetail.forHowManyPeople) and the per-line label on the bag row and account order history (itemDetail.forPeople) render the Dutch text Voor hoeveel personen / Voor {count} personen in every language -- both keys exist only in theenandnllocale files, and theenfile literally holds the Dutch string. The KDS board ships anenlocale only, so it always says "For {count} people". Back office is fully translated (transactions.order_detail.for_peoplein all seven locales) and the emailed receipt is rendered server-side in the order's locale. Do not promise a translated storefront label to non-Dutch merchants.
Live POS Inventory
By default the Inventory page shows the stock counts from the last sync. Live POS Inventory is an optional MplusKassa-only mode that, once enabled, has the back office pull current stock straight from MplusKassa for the items visible on the page and patch the stock column in place.
- It is off by default, stored on the integration as
settings.live_pos_inventory_enabled, and toggled from the Inventory overview page -- not from the MplusKassa page. The toggle is only shown for MplusKassa merchants. - The refresh is scoped to the currently visible items, so it stays fast regardless of catalog size.
- A location with no MplusKassa branch mapping returns 422 "This location is not connected to MplusKassa."
- Polling errors are deliberately silent (a transient POS hiccup does not raise an error toast); the table simply keeps showing the last known values.
Actions
Connect MplusKassa
Route: /mpluskassa
- Complete all prerequisites (see checklist below).
- Enter your MplusKassa API connection details:
- API ident -- your MplusKassa API ident (from your Mplus partner portal / account manager)
- API secret -- your MplusKassa API secret
- API URL (optional) -- defaults to the standard MplusKassa endpoint
- API port (optional)
- Click Connect. Upvendo imports your MplusKassa branches as locations.
Configure Branch and Workplaces
Once connected, choose the branch and the workplaces that orders are injected into:
- Online ordering workplace -- where Online Ordering orders are sent (usually a MOBILE/ONLINE workplace)
- Table QR workplace (optional) -- where Table QR orders are sent; falls back to the online workplace if not set
Sending different channels to different workplaces improves POS reporting.
Set the Daily Sync Time
Set a sync time (whole or half hour) for the automatic daily catalog/inventory sync. Leaving it blank does not switch the daily sync off -- MplusKassa integrations without a sync time fall back to 03:30.
The time you enter is your local time, stored as that local wall-clock rather than converted to UTC, so a
03:30sync stays at 03:30 all year and does not drift when the clocks change. This is now the same behaviour across every in-house POS integration; the earlier MplusKassa-specific "entered time is UTC" quirk is gone. The zone is resolved from your locations (falling back toEurope/Brussels) and shown as a caption under the field -- it is not something you pick.
Sync Now
Clicking Sync Now opens a dialog to choose the sync mode before it starts:
- Incremental Sync (default, pre-selected) -- fetches only products/articles that changed since the last stored sync marker. Faster; recommended for routine updates.
- Full Sync -- re-pulls the entire catalog from MplusKassa starting at sync marker
1, and additionally runs orphan detection: once the sync finishes, any previously-imported, currently-active item whose MplusKassalast_synced_atwas not refreshed during the run (i.e. MplusKassa no longer returned it) is set to Inactive with adeactivated_reasonoforphan_not_in_mplus. Use Full Sync when an item was deleted in MplusKassa but still shows (active) in Upvendo, or when the catalog looks out of sync generally. Slower than incremental.
The chosen mode is sent as sync_type (incremental or full) to POST /back-office/mpluskassa/start-sync; omitting it (or picking incremental) behaves the same as before this dialog existed. Whichever mode runs, the sync maps Products to Categories and Articles to Items, downloads images to Cloudflare Images, imports preparation methods / article alterations as modifier groups, and updates VAT rates and stock. The sync refuses to start (returns a 409 with "A sync is already in progress") if one is already in progress.
Complete Location Setup
After branches import, go to Settings -> Locations (/settings/locations) to complete fields the POS does not provide (address coordinates, business hours, delivery zones, etc.). Imported locations are assigned the default billing profile's Stripe customer ID and the merchant's default branding profile.
Disconnect
Disconnecting is administratively disabled -- the back office surfaces a disconnect option, but the request is rejected with a message to contact an administrator.
Business Rules
- MplusKassa is the source of truth for the catalog, prices, VAT and stock. The synced fields are locked in Upvendo (see "MplusKassa Owns the Catalog").
- The menu sync is one way (MplusKassa -> Upvendo). Catalog changes in Upvendo are not pushed back to MplusKassa during normal operation.
- Inventory is one way (MplusKassa -> Upvendo). The sync reads
amountFreeper article per branch. - Branch-specific price deviations are honoured: a branch's article price overrides the default for that location.
- An item with a MplusKassa bestelbeperking can only be ordered for a party size in its allowed set; the number of dishes is unrestricted. The backend checks
items.*.persons(falling back toquantityfor clients that predate the field) and rejects anything else with a 422 on both Kiosk and Online Ordering. Change the set in MplusKassa and re-sync. An empty set means no restriction. - Live POS Inventory is MplusKassa-only and off by default. When enabled from the Inventory page, the stock column is refreshed from the POS for the visible items instead of showing only the last synced counts.
- Transient MplusKassa failures during the catalog sync are retried automatically -- up to 3 attempts, waiting 2s then 4s between them. Retryable: connection errors, timeouts, any upstream 5xx (500/502/503/504) and MplusKassa
[Server]faults.[Client]faults (e.g. "method not implemented") are not retried and fail fast. Repeated rate-limit responses widen the sync's per-call throttle to 1.5s. - Sync Now lets the user pick Incremental (default, resumes from the last sync marker) or Full (
sync_type=full, re-pulls from marker1and deactivates items no longer returned by MplusKassa -- orphan detection). - Orphan detection runs whenever a sync starts at marker
1-- whether that came from a manual Full Sync or from the daily marker-reset reconciliation. The scheduler resets the marker once per calendar day, so the first scheduled run of each day is effectively a full sync and does deactivate items MplusKassa no longer returns. Subsequent scheduled runs that day are incremental and do not. - If no sync time is set, MplusKassa integrations default to a
03:30daily sync, read as local time in the integration'ssync_timezone. - Orders flow one way (Upvendo -> MplusKassa) and are forwarded automatically once an order is complete (or awaiting-invoice). There is no manual-acceptance hold.
deliverOrderis only sent whenuse_deliver_orderis enabled.extOrderId(the Upvendo order number) is the idempotency / recovery key for the MplusKassa order. MplusKassa requires a relation number; the integration resolves or creates one, with an anonymous fallback.- VAT method is always
INCLUSIVEfor orders sent to MplusKassa; imported tax rates are stored as inclusive. - Webhooks are HMAC-signed; an invalid or missing signature is rejected. The webhook controller returns the handler's JSON verbatim (the external-payment events depend on the raw
externalPayment/polling.finishedfields). cancelSessioncancels the matching Upvendo transaction;creditSession(refunds) is acknowledged but not processed -- refunds made on the POS are not reflected in Upvendo.completeSessiontransactions do not link an Upvendo customer, accrue loyalty, or split gift-card tenders.determinePricinghas a 60-second per-location cooldown; the checkout gate forces a fresh quote past the cooldown.
Prerequisites Checklist
Before integrating with MplusKassa, complete these steps:
1. Branding Profile Setup (Required)
- Go to Settings -> Brand (
/settings/brand). - Configure your store's branding (logo, colors, etc.).
- The default branding profile is assigned to imported locations.
Payment and billing profiles are optional at connect time -- you can set them up later. Only a branding profile is enforced by
ThirdPartyIntegrationHelper::checkRequiredProfilesfor MplusKassa.
2. Payment Profile Setup (Recommended)
- Go to Settings -> Payments (
/settings/payments). - Set up your payment providers (e.g. Stripe for Online Ordering, Viva Wallet for Kiosk terminals).
- Configure payment methods (iDEAL is common for the Netherlands; cards; Bancontact for Belgian customers).
3. Billing Profile Setup (Recommended)
- Go to Settings -> Billing (
/settings/billing). - Complete your billing information.
- The default billing profile's Stripe customer ID is assigned to imported locations.
Order Flow
text
Upvendo (Kiosk / Online Ordering / Table QR)
|
Customer places order and pays
|
Order reaches complete (or awaiting-invoice) status
|
ProcessOrderJob forwards the order to MplusKassa
- Maps items to MplusKassa article numbers
- Sets branch / workplace, resolves a relation number
- vatMethod = INCLUSIVE, extOrderId = order number (idempotency)
|
MplusKassa SOAP API
- createOrder -> payOrder (-> deliverOrder if enabled)
- createOrderV3 (atomic create + pay) for some channels
- createAndPayTableOrder / saveTableOrder for dine-in
|
On failure: order flagged + retry scheduledNetherlands / Belgium Notes
Payment Methods
Common choices for Dutch and Belgian customers:
- iDEAL -- popular in the Netherlands (via Stripe)
- Card -- Visa, Mastercard
- Bancontact -- for Belgian customers
VAT (BTW)
VAT is not configured by a tax-mapping screen in Upvendo. Each MplusKassa article carries its own VAT code/percentage, and Upvendo auto-creates a matching tax rate (MPLUS_VAT_{code}, or MPLUS_VAT_{code}_{altCode} when the article's alternative/takeaway VAT code differs) with the inclusive tax method when the article is imported. Branches can be in the Netherlands or Belgium; the integration derives country, timezone and language from the MplusKassa branch.
FAQs
Q: Can I edit menu items or prices in Upvendo? A: Not for fields that come from MplusKassa. MplusKassa owns the catalog, so item name, description, price, PLU, modifier groups, allergens and dietary preferences (and category name/description) are locked in Upvendo. Make those changes in MplusKassa, then sync. Fields that do not come from MplusKassa stay editable.
Q: Is the menu sync bidirectional? A: No. It is one-directional, from MplusKassa into Upvendo. Upvendo does not push catalog changes back during normal operation.
Q: Do I need to create locations manually? A: No. When you connect MplusKassa, your branches are imported as locations. Complete the missing fields (coordinates, hours, delivery zones) in Settings -> Locations afterwards.
Q: How does the external payment flow work? A: MplusKassa starts the payment (startExternalPayment), Upvendo creates a pending payment and the POS polls (pollExternalPayment). Upvendo answers with confirmed/finished flags when the payment succeeds, and the POS completes the sale.
Q: Are refunds supported? A: No. Refunds made on the MplusKassa POS (creditSession) are acknowledged but not processed in Upvendo.
Q: Does the integration support table ordering? A: Yes. Dine-in orders with a table number use a create-and-pay table-order call; other table workflows use save/pay table-order calls.
Q: What's the difference between Incremental Sync and Full Sync? A: Clicking Sync Now opens a dialog to pick the mode. Incremental (the default) only pulls what changed since the last sync marker and is faster. Full re-pulls the whole catalog from marker 1 and also runs orphan detection: any active item that MplusKassa no longer returns is deactivated in Upvendo. Use Full Sync if an item was deleted in MplusKassa but still shows as active in Upvendo, or if the catalog generally looks out of sync.
Q: I deleted an item in MplusKassa but it still shows as active in Upvendo -- why? A: Item deactivation only happens during the orphan-detection step, which runs when a sync starts from marker 1. An ordinary incremental sync does not deactivate items, so a just-deleted article can linger until the next daily reconciliation run (which restarts from marker 1 and clears it, normally within 24 hours). To clear it immediately, run Sync Now and choose Full Sync.
Q: What happens if the MplusKassa API is unreachable during sync? A: Transient failures are retried first -- each catalog call gets up to 3 attempts, waiting 2s then 4s in between. Connection errors, timeouts, upstream 5xx responses (500/502/503/504) and MplusKassa [Server] faults are retried; [Client] faults (such as "method not implemented") are not, and fail immediately. If it still fails, the sync surfaces the error (check the Activity Log / integration logs). Because sync uses markers, a later successful sync picks up where it left off. For relation-aware pricing, a failed determinePricing opens a 60-second cooldown that serves a fallback price until it clears.
Q: Why can't a customer order 5 of this item? A: The article has a bestelbeperking in MplusKassa -- a fixed list of allowed order quantities ("Voor hoeveel personen"). The storefront and kiosk only offer those values, and the backend rejects anything else with "can only be ordered in these quantities". Edit the allowed quantities on the article in MplusKassa and re-sync; clearing the restriction there makes the item freely orderable again.
Q: Can I stop orders from being sent automatically to MplusKassa so I can accept them first? A: No. There is no setting to hold orders for manual acceptance. Once an order is placed and reaches a syncable status (Kiosk, Online Ordering, or Table QR), Upvendo forwards it to MplusKassa automatically. What you can control is whether the online channel accepts orders at all (the Accept online orders switch), the order cut-off time, and the per-slot order/item capacity (Order Capacity) in Online Settings.
Troubleshooting
Orders not syncing
- Verify the connection status shows "Connected" on the MplusKassa page.
- Check the Activity Log / integration logs for sync errors.
- Ensure the order's items have a valid MplusKassa article-number mapping (items without
external_ids.mpluskassawill fail). - Confirm the branch and workplace are configured for the channel.
Payment failing (external payment)
- Verify a payment profile is assigned to the location and the provider is connected.
- Check that the webhook endpoint is reachable and the signature verifies.
- For iDEAL issues, verify the customer's bank is supported by the provider.
Stock not updating
- Confirm the items have a valid
external_ids.mpluskassamapping (stock is matched by article). - Trigger a manual sync (MplusKassa -> Sync Now).
- Instead of a full manual sync, enable Live POS Inventory on the Inventory page to read current stock straight from the POS for the items you are looking at.
- Remember stock is one-directional (MplusKassa -> Upvendo only).
Menu/price changes not appearing
- Make the change in MplusKassa, then sync (the change cannot be made in Upvendo because the field is locked).
- Confirm the sync ran (check logs / sync markers).
- If an item was deleted in MplusKassa and still shows active in Upvendo, an incremental sync will not deactivate it. The daily reconciliation run picks it up within 24 hours; to clear it immediately, run Sync Now and choose Full Sync to trigger orphan detection.
Locations not imported correctly
- Check that your MplusKassa account has active branches.
- After import, complete the missing fields in Settings -> Locations.
- If a branch was added after the initial connection, run a sync to detect it.
Webhook events not being received
- Verify the webhook subscription is registered in MplusKassa and the integration is enabled.
- Check that
/webhook/mpluskassa/{event}is reachable from the internet. - A
400 INVALID_SIGNATURE(or missing-headers) response means the HMAC signature /X-Mplus-Subscription-Iddid not match -- check the configured webhook secret.