> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loyalty.xeniamoments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration and security

> Per-venue activation, webhook registration, request signing, secrets, environments and hosts, limits and timestamps.

## Per-venue activation

Activation happens on both sides and only takes effect when both are done.

| Side | What is configured | Where |
| - | - | - |
| OnlinePOS | Nexi Engage module on/off; REKOM module on/off; the two REKOM endpoint URLs; the signing secret; "Nexi Engage Store ID" (BAX ID) per terminal setup | Backoffice / webhook registration through the REST API (self-service, per V1 §10) |
| REKOM | OnlinePOS venue ID and BAX number(s) → REKOM venue and programme; environment (staging/production); signing secret | REKOM backend configuration |

<Warning>
  **Open — OnlinePOS.** The mechanics of webhook registration are not yet described:
  what exactly is registrable (URL per event? one base URL?), whether registration is
  per venue or per partner with per-venue activation, the event names, and where the
  signing secret is entered. REKOM's proposal: one base URL and one secret per
  environment, registered once per partner, with REKOM able to update the secret
  itself; activation per venue in Backoffice.
</Warning>

<Note>
  **Directional.** Field names, enum values, headers and JSON structures on this page
  show *which* information is exchanged, not the final names. None of them has been
  aligned with OnlinePOS yet. REKOM will adopt whatever OnlinePOS has by default for
  its webhooks (header names, signature format), and does not require any of these specific names.
</Note>

## Request signing

<Info>**Proposed — for OnlinePOS confirmation.** Bearer tokens are the fallback if OnlinePOS's HTTP client cannot sign requests.</Info>

Every request from the OnlinePOS backend to the REKOM backend carries an
HMAC-SHA256 signature over a timestamp and the raw body.

| Header | Value |
| - | - |
| `OnlinePOS-Signature` | `t=<unix seconds>,v1=<hex digest>` |
| `Idempotency-Key` | The `calculationId` (both endpoints) |
| `X-Request-Id` | A fresh UUID per HTTP attempt, for log correlation |
| `Content-Type` | `application/json; charset=utf-8` |

The digest is computed as:

```
signed_payload = "<t>" + "." + <raw request body bytes>
v1 = hex( HMAC_SHA256( secret, signed_payload ) )
```

Verification on the REKOM backend:

1. Parse `t` and `v1`. Reject with `401 invalid_signature` if either is missing.
2. Reject with `401 stale_timestamp` if `t` differs from the server clock by more
   than **300 seconds**.
3. Recompute `v1` over the exact bytes received, with every active secret
   (see Secrets below), and compare in constant time. Reject with `401 invalid_signature`
   on mismatch.

Example (secret `whsec_test`, timestamp `1791456000`, body `{"calculationId":"…"}`):

```http theme={null}
POST /v1/onlinepos/basket HTTP/1.1
Host: loyalty-staging.xeniamoments.com
Content-Type: application/json; charset=utf-8
Idempotency-Key: 6f1d2c3e-8a4b-4c5d-9e0f-1a2b3c4d5e6f
X-Request-Id: 0b7a7a4e-0c1b-4e2a-9f3d-5c6d7e8f9a0b
OnlinePOS-Signature: t=1791456000,v1=3f0a…c9e1
```

Responses from the REKOM backend are not signed; TLS plus the request-bound
`calculationId` in the body are sufficient, since OnlinePOS initiated the request.

### Secrets

* One secret per **environment** (staging, production), generated by REKOM and
  handed over out of band (password manager share, never e-mail).
* **No scheduled rotation is planned.** Instead REKOM asks for **access to update the
  secret itself** in OnlinePOS's webhook configuration (the self-service registration
  from V1 §10), so a compromised or expiring secret can be swapped without a ticket.
* When a secret is swapped, REKOM keeps **both the old and the new secret active with
  an overlap** until OnlinePOS's side is confirmed on the new one, so there is no
  interruption. Signatures are verified against every active secret.
* Secrets are at least 32 random bytes, presented base64 or hex.

### Transport

* TLS 1.2 or newer; HTTPS only; HSTS on the REKOM hosts.
* Certificates from a public CA; no pinning required.
* The REKOM backend publishes a stable set of egress IPs for its REST sync calls if
  OnlinePOS wants to allowlist them, and asks OnlinePOS for the same if REKOM is to
  allowlist inbound traffic (open point).

## Environments and hosts

<Note>Both hosts are **TBC** until DNS is live. They will be confirmed in the API reference.</Note>

| Environment | REKOM host | OnlinePOS side | Nexi side |
| - | - | - | - |
| Test | `https://loyalty-staging.xeniamoments.com` | OnlinePOS test venues and test terminals | Test BAX IDs registered as Nexi test stores |
| Production | `https://loyalty.xeniamoments.com` | Production venues | Production BAX IDs |

* Separate secrets per environment.
* A venue mapped to one environment that calls the other host is answered
  `422 venue_not_configured`; the POS treats it as `failed`.
* The staging host returns the same latency characteristics as production, so
  timing can be measured there.

## Limits

<Info>**Proposed — for OnlinePOS confirmation.**</Info>

| Limit | Value | On breach |
| - | - | - |
| Lines per basket | 200 | `422 too_many_lines` |
| Request body | 256 KB | `413` / `422 invalid_request` |
| Text fields | `lineDiscountText` 40 chars, `memberLabel` 40 chars, `receiptFooterText` 200 chars | Truncated by OnlinePOS if its layout is shorter |
| Rate | 50 requests/second per environment sustained, bursts to 200 | `429 rate_limited` with `Retry-After`; the POS treats it as `failed` |
| Idempotency window | 24 hours | — |
| Calculation expiry | 30 minutes without sale event or supersession | — |

## Timestamps and clocks

* All timestamps in payloads are **RFC 3339 / ISO 8601 with UTC offset**, e.g.
  `2026-10-08T21:14:03+02:00` or `2026-10-08T19:14:03Z`.
* Signature timestamps (`t`) are Unix seconds, UTC.
* Terminals and POS hosts must be NTP-synchronised; a skew beyond 300 s makes
  signatures fail. **Open — OnlinePOS:** confirm NTP on Windows POS hosts and the
  timezone semantics of the REST API's `datetime` fields used in the sync.

## Logging and support

* Both sides log `X-Request-Id`, `calculationId`, venue, status and timings for
  every call, retained for at least 90 days.
* The REKOM backend returns `X-Request-Id` (echoed) and `Server-Timing` on every
  response.
* Support path: OnlinePOS raises issues with the GUID; REKOM answers with the
  stored request/response pair. Nexi has offered a shared Slack channel once work
  starts (open point).
* No card data, PAN or token ever reaches the REKOM backend; the only identifier is
  the loyalty ID.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.