Register, pull and read the queue
Part 1 of 4 · Reading iVendNext's events reliably
Back: the overviewNext: The labels, the reconciliation and the reference client
1. Register your application (the install does this)
A manifest installs your Registered Application (registered_applications, manifest v2), on, with one feed per app. From POS version 2.1.0.667 the install also makes your app's own user, <app>@integration.invalid, and names it on the registration as the user your service reads as. That user holds one role, <PREFIX> Integration, and works through the API only. The record types you follow (POS Invoice, Sales Invoice, Customer, Item, Stock Entry …) are on from the install; the retailer's administrator gives the role the reads your app needs. A hosted app is then connected by the site itself, after the install or an update (Connect again only if the site could not reach the app, or a key was lost or changed); an app you host yourself gets its key when the retailer presses Generate Keys on that user. An app installed before 2.1.0.667 gets its user at its next version, if its feed names no user. From then on, every change to a followed record adds one row to your queue — after the change commits, so a refused save sends nothing.
Changes made by your own integration user are not sent to you (no echo of your own writes).
Switching it on by script. A partner who sets a site up from a configuration script, for an app whose registration the install did not make, does three things, not one:
- Set the registration's user to the integration user (a registration the install made already names the app's own user).
- Tick each record type on the registration:
registered_application_events[].enableis1on every row you follow. iVendNext writes a feed row only for a ticked record type. A feed with the user set and no row ticked reads empty. - Expect the row a moment after the save commits. iVendNext writes it in a background job. A worker that polls every few seconds is fine; a script that saves a record and reads the feed at once finds nothing. In a test, poll until the feed has been quiet for two passes. (Sample S22's
switch-ondoes the first two parts.)
2. Pull, act, mark done — in this order
loop:
page = events(limit=100) # the OLDEST not-done events, oldest first
for each event in page:
if event.id in my seen-set: skip # a repeat: already acted on
in ONE transaction of my own: # my business write + the seen-set entry, together
act on the event # (read the record over the API if I need its content)
add event.id to my seen-set
on failure: failed(event.id, note) # it stays not-done, with the note, for the next pass
done(ids of the page) # one call
- A queue, not a cursor. You always ask for the oldest events not yet done. A row a slow worker commits late is never skipped behind a cursor.
- At least once. An event fetched and not yet marked done comes back. Your seen-set, written in the same transaction as your own work, makes the effect exactly once — for writes into your own database. A write you make somewhere else (back into the retailer's iVendNext over the API, or to a third system) cannot share that transaction, so make it idempotent too: key it on the record or line the event names, and treat "already exists" as done. S3's outside service names each deviation after the sale line, so a second delivery answers 409 and writes nothing.
- Events carry no content. An event says which record changed and how (
sale.booked,customer.created…). Read the record over the API with your own key when you need it — it is always the current version. - Mark done, every time. iVendNext clears only rows that are done (after 30 days). An application that never marks its rows done grows its queue for ever.
3. Two ways to read the queue, one client
The client reads the queue in one of two ways (its --transport). Your handler is the same for both.
The feed every retailer has today (--transport engine) |
The scoped feed, the door (--transport door) |
|
|---|---|---|
| Calls | the record API on Integration Sync Log (list your rows, mark them done with flag = 1, a note in error) |
events · done · failed, each a POST (the event feed page) |
| The key needs | read + write on Integration Sync Log and read on the record types you follow — the narrowest grant that works | read on the record types you follow — nothing on the log |
| Scope | ⚠ none on the server. The key can read and mark done every application's rows, not only yours. The client filters to its own rows, but that is a convention, not a control. Use it only where your service is the retailer's one consumer | the server's: only the caller's own application's rows. Another application's event is refused |
Labels (sale.booked …) |
applied by the client, from the same published table | decided by the server |
| Status | ✅ works on every retailer today | ✅ live: the same POST call was read on a test site on 2026-10-09; the client's crash-and-restart test ran only against our stand-in for it |
Write your handler against the labels and both ways run it unchanged. Prefer the door where your service is not the retailer's only consumer (--door-prefix ivendnext_mpos.api.feed, the default).
Back: the overviewNext: The labels, the reconciliation and the reference client