iVendNextDevelopers Request a sandbox

14b. Deploying your app

How to run your own service so a shop can reach it: what it must do, how to package it, where it can run, and how to update it without breaking a shop.

For: a builder whose work includes an outside app (chapters 5 and 6): a service that answers a till key, reads the event feed, or reacts to a webhook, and must run on a server a shop can reach. Reference implementation: ../tools/app_template/ — a small service, a Dockerfile, a settings file and a checker, ready to copy.

The short answer

When to use it

When your app is ready to leave your laptop: before a shop's sandbox calls it for the first time, and again for each customer's production.

What every deployable app does

  1. Has a public HTTPS address with a valid certificate. The shop checks the certificate against your host name, and an address that resolves to a private network is refused (recipes/point_handler.md).
  2. Answers inside the time. A till key has 3 seconds unless the shop raises it, 10 at most. Do the slow work before the call, or after it.
  3. Verifies every call before it reads it. The signature is base64(HMAC-SHA256(secret, "<timestamp>.<raw body>")) over the raw body, and a timestamp more than five minutes from your clock is refused. Answer a body over 64 KB with 413.
  4. Keeps secrets out of the image and the repository. The signing secret and the API key arrive as environment variables from the host's secret store.
  5. Has a health address (/health) that answers without a signature and says nothing about shops.
  6. Logs one line per call: the request id, the status and the milliseconds. Never the body, the sale, the customer, the secret or the signature.
  7. Is safe to run twice. A webhook is tried three times and then dropped, and a call can arrive again. Handle each event so a repeat changes nothing, and run a reconciliation pull on a schedule (chapter 5).
  8. Stops cleanly. A host stops a container with a termination signal: finish the call in hand, then exit.

The template does all eight. Replace its core.py with your logic and keep the rest.

Steps

  1. Copy the template folder to your own repository and rename the app in app.json.

  2. Run it on your laptop and test it without a shop:

    python3 -m unittest discover -s tools/app_template/tests -p 'test_*.py' -v
    
    IVN_POINT_SECRET=any-long-random-string docker compose up --build
    
  3. Call it the way the till does with the stand-in caller (tools/standin_caller/), which signs a call and judges the answer as the remote till key does.

  4. Check the settings file and the Dockerfile:

    python3 tools/app_template/validate_app.py
    

    It refuses a container that runs as root, has no health check, exposes a port the settings file does not name, or has a secret baked into it; and a settings file with a value in it.

  5. Publish the image to a container registry you control, with a version tag (1.0.0), never only latest.

  6. Run the image on a host (below) with the settings from app.json.

  7. Tell the shop where you are. Put your HTTPS address and the signing secret in the shop's record for the key, the webhook or the feed (chapters 5 to 7). tools/deploy prints a generated secret once: store it in your host's secret store the same minute.

  8. Call it once from the shop (press the key, or send a test webhook) and read your log line.

Where it can run

Any host that runs a container and gives it a public HTTPS address will do. What to set:

Setting Value
Image your registry and version tag
Port the one in app.json (8080 in the template)
Health check GET /health, expect 200
Environment the names in app.json; secrets from the host's secret store, never typed into a file
Instances at least one always running (a till key waits seconds, not a cold start)
Address HTTPS with a certificate for your own host name
Region near the shop's site, and where the customer's data may live
Restart automatic, on a failed health check

A managed container service in your own cloud account is the least work. A small server running docker compose also works. If your app keeps its own data, give it its own database with daily backups, and write down how you restore it.

Let CitiXsys host it

If you would rather not run a server, CitiXsys runs your app for you on our own cloud, beside iVendNext. Your code stays in your own repository. We never need your source code or access to your code repository: we run the image you build.

Build the image for ARM64 (docker build --platform linux/arm64 …). Our hosting runs ARM processors; an image built only for x86 does not start.

What you send us — one hosting order:

Check it first, on your machine. The same checks we run, and what we would create; it changes nothing:

python3 tools/deploy_app/deploy_app.py <your app folder> --image <registry>/<repository>:1.0.0 --plan starter --partner <your name>

Order it from your CitiXsys partner manager. A licensed customer's IT team and CitiXsys's own teams, such as the CitiXsys Consulting Team, order hosting the same way. Ordering and paying online is coming. Not an iVendNext Partner yet? Start with the iVendNext Partner form.

What it costs: a price per app per month, in US dollars (below), covering the copies the plan runs (sandbox, staging). It is billed monthly, in whole months from the month the app goes live, with no minimum term: either side can stop at any month end. Whoever orders the hosting pays: you, or the customer the app is built for when that customer orders it. App Hosting is billed on its own, never as a line on an iVendNext subscription invoice. An app that only answers presses and is listed in the App library carries no hosting charge. The managed database, when it is offered, is priced separately.

What you get (for a container; a function's list is in A function or a container): the address https://<app>.ivendnext.app with its certificate; your secrets kept in our secret store (we ask you for them when the app goes live; they are never in a file); a restart when the health check fails; a firewall that lets in only our load balancer; and no rights in our cloud for your app's code. Your app's log lines are kept for the plan's days, and we send them to you when you ask.

Plan Per app, per month For What the platform sets
Starter $49 a till key or check handler, low volume size small · one instance · logs kept 14 days · a sandbox copy on request
Standard $129 a connected app small or medium · one instance · logs kept 30 days · a staging copy
Managed $399 apps built by the CitiXsys Consulting Team, and anyone who wants us to run it any size · two instances · logs kept 90 days · we check the app before it is connected and test it before each iVendNext upgrade, and patch the apps whose source we hold

Not offered yet: a database for your app, backups of its data, and alerts when it is down. A hosted app has no disk of its own to keep data on, so an app that keeps data waits for the managed database, which is priced separately: ask us when it will be offered.

An app that only answers presses runs as a function, paid per call, not as an always-on container: see A function or a container in this chapter.

A new version: send the new version's tag on the same order. Shipping updates from your own GitHub, with a permission limited to your one app, is coming.

The part inside the shop is separate. Your manifest reaches each customer's site through its administrator's install screen, with the customer's consent (chapter 14a); tools/deploy puts it on your own sandbox. Hosting runs only your outside service. An app that is not a container in this template's shape is a Managed job: ask us.

What the checks refuse, and the technical notes: tools/deploy_app.

One app, three copies

Run the same image as sandbox, staging and production. Each copy has its own shop address, its own API key and its own signing secret. Never share a secret between copies. Use tools/deploy to put your manifest on your sandbox; on staging and production the shop's administrator installs it (chapter 14a). Register each copy's address in that shop only.

One app, many shops

An app listed in the App library is installed by shops you have never met. Each shop's install makes its own secrets, so your app has to learn them from the shop. It does that with one handshake, and the template has it built in.

⚠ The site connects by itself only to an app hosted at an *.ivendnext.app address and installed from the App library. For an app on your own server, or one delivered on a customer's Made for you shelf, the shop's secrets reach you by hand, and you run the template for that one shop (IVN_POINT_SECRET).

To switch the template to many shops, set IVN_LIBRARY_APP to your app's id in the App library, give it a store (IVN_SHOPS_FILE, or "shops": true in app.json on CitiXsys hosting, which makes your app its own encrypted table), and name your till key in app.py (KEY). Sample S22 runs this way: one service for every shop, with a feed poll for each.

Technical notes

Call What the site sends Your app answers
POST /ivendnext/connect header X-iVendNext-Site: <site>; body {"key": "IVN1-…", "site": "<site>", "secrets": {"<name>": "<value>", …}}, unsigned 200 {"connected": "<site>"} · 403 {"refused": "key_form" | "key_signature" | "key_other_app" | "key_other_site"} · 400 {"refused": "bad_request"} · 503 {"refused": "unavailable"}
POST /ivendnext/disconnect the same header; body {"key": "IVN1-…", "site": "<site>"} 200 {"disconnected": "<site>"}, or the refusals above
every other call X-iVendNext-Site: <site> beside the signature headers the call's own answer; 401 for a site it does not know or a signature that is not that shop's

A function or a container

When CitiXsys hosts your app, it runs in one of two ways. Your code is the same in both.

How to choose: if your app does nothing until a shop calls it, it is a function. If it does anything on its own, it is a container. Sample S18, the members-only rule guard, runs as a function. Sample S22, customer-owned loyalty, reads the feed, so it stays a container.

A function serves many shops (the handshake above) and keeps no secret of its own: each shop hands over its own when it connects. An app that needs its own secret runs as a container. ⚠ A shop that cannot connect itself (above), such as one your app reaches through its Made for you shelf, cannot hand a function its secrets: host a one-shop app as a container.

Say which in app.json: "runs": "function", with "handler" (the file and function the call goes to) and "package" (the files it is made from). An app that says nothing runs as a container, exactly as before. deploy_app checks it and writes the package; send that file instead of an image.

Technical notes

Updating without breaking a shop

What is refused, and why

When your server is down

The shop keeps selling. A till key shows the cashier "⟨key⟩ could not finish. Nothing was added to the sale." A webhook is tried three times and then dropped; your reconciliation pull catches up when you are back (chapter 5). Chapters 5 and 6 list what the cashier reads in each case.

The tests

Your app is ready to connect when the template's checks pass for your copy, validate_app.py says READY, the stand-in caller gets your answer, a call with the wrong secret gets 401, and the first real press from a sandbox shows one clean log line.

See also

Security and the handshake · Host with CitiXsys or run your own · 5. Outside apps and the event feed · 6. Till keys · 14. Testing and release · 14a. Deploying to a chain of stores · A till key

This page in the kit