Skip to content

External Shop POS bridge

If your campground shop runs on its own till — Lightspeed, Zettle, a custom Python script, anything — this bridge lets the till post each transaction to CampOne so the day’s totals (and the SAP daily journal, and the reports) capture all revenue, not just the front desk.

Unlike the other integrations, this one is inbound: the external POS calls CampOne, not the other way around. You don’t have a vendor account to configure here — instead, you mint a CampOne API key with the right scopes and paste it into the external system.

  • A public API at /api/v1/public/pos/transactions/ that accepts external transactions
  • Idempotent on external_ref — retrying after a network blip never creates duplicates
  • Standard CampOne API key auth, with two new scopes: pos:read and pos:write
  • A read-back endpoint for end-of-day reconciliation
  • A products endpoint so the external POS can sync the catalogue
  • Inbound activity log on the settings card

None on the vendor side — you don’t need a Lightspeed contract or a Zettle account to enable the integration. You configure CampOne, then point your external system at it.

What you do need:

  • An external POS system that can make HTTP requests with a Bearer token
  • Knowledge of how to enter API URLs / tokens in that system

Settings → Integrations → API Keys (the existing API-key surface, alongside the Vendor Adapters tab):

  1. Click Create new key.
  2. Give it a name (e.g. Lightspeed shop till).
  3. Tick pos:read and pos:write scopes.
  4. Click Generate. CampOne shows the raw token once — copy it now and save it in the external system.
  5. The token is hashed at rest; if you lose it, revoke and mint a new one.

Then, in your external POS, configure:

  • API URL: https://your-tenant.campone.ch/api/v1/public/pos/transactions/
  • Auth: Authorization: Bearer <the-token-you-just-copied>
POST /api/v1/public/pos/transactions/
Authorization: Bearer ck_…
Content-Type: application/json
{
"external_ref": "lightspeed-2026-04-29-T-1234",
"payment_method": "CASH",
"total_amount": "12.50",
"items": [
{ "description": "Croissant", "quantity": 2, "unit_price": "3.50", "line_total": "7.00" },
{ "description": "Coffee", "quantity": 1, "unit_price": "5.50", "line_total": "5.50" }
],
"occurred_at": "2026-04-29T08:14:32Z"
}

The external_ref is your idempotency key — it must be unique per transaction in your system. CampOne enforces uniqueness per tenant, so retrying the same transaction returns a 200 with the existing record (instead of a 409 or a duplicate).

End-of-day, the external system can GET /api/v1/public/pos/transactions/<external_ref>/ to confirm CampOne has the transaction. Or GET /api/v1/public/pos/products/ to refresh its catalogue.

Inside CampOne, transactions from external sources show in /kasse/transaktionen like any other POS transaction, with Source: External POS so you can filter.

  • API keys are hashed at rest with Django’s password hasher (Argon2 / bcrypt).
  • Every API call is logged: method, path, status code, latency, key prefix.
  • Keys can be revoked instantly. Revoked keys return 401 from the next call onwards.
  • Scopes are enforced per endpoint — a pos:read-only key can’t post transactions, even if you ask it to.
  • Live integrations against specific external POS vendors (Lightspeed, Zettle, Sumup) are not yet certified — the standard HTTP contract above is what they need to fit into. Roadmap: tested adapters per vendor.
  • Refunds posted from the external POS appear in CampOne as separate transactions; CampOne doesn’t auto-link a refund to its original sale across systems.