Skip to content

Security model

Who trusts what, and what is never trusted. This page explains why; a printable go-live checklist is «удахгүй».

1. Trust boundaries

            trusted                                  not trusted
 ┌──────────────────────────────┐        ┌────────────────────────────────────────┐
 │ Your backend (client secret) │        │ Browsers, mobile apps, terminals,       │
 │ Gerege API (TLS)             │        │ machines, stickers, QRs, return_url,    │
 │ Gerege App's payment sheet   │        │ unsigned webhook bodies, bridge answers │
 └──────────────────────────────┘        └────────────────────────────────────────┘

2. No secret on a terminal, a machine or an app

A client secret can create, capture, cancel and refund every payment of your app. On a machine, a kiosk, a mobile app or in a browser, someone will extract it. So:

  • Machine / app → your backend (with its own authentication) → Gerege. The machine only displays pay_url or the terminal's url.
  • Keep the secret in an environment variable or a secret manager; never in a repository, a log, an error report or a frontend bundle.
  • Least privilege: give each key only the scopes it needs (a preset), a separately named key per server, an IP allowlist where you can. A BI script should not be able to refund.
  • If it leaks, revoke it at once (POST /dev/apps/{id}/keys/{kid}/revoke — no fresh sign-in, never in your way) and create a new one; for a planned change use …/roll (the old one keeps working through the overlap). The default pair is replaced at once with POST /dev/apps/{id}/credentials.
  • A test key cannot write to the terminal and sub-merchant registry (403 live_only) — a leaked sandbox secret cannot redirect a real machine's money.

3. Only the person moves money

  • You ask for a payment; the person sees the amount in Gerege App's own sheet, picks the account and confirms with Face ID. Your app, website or machine cannot press «pay» for them.
  • A URL never carries an amount. pay_url, a terminal's url and Gerege App's gerege-app:// links never prefill a payee or an amount — the amount always comes from the server's intent. Swapping a QR cannot change it.
  • The bridge's requestPayment answer and return_url?pi= are signals, not proof: read the outcome from your backend.

4. Registry names on the payment sheet

The person sees whom they pay from the registry, not from your text:

What From Why
The app's name and icon the registry, approved in review a phishing app cannot use another brand's name
The terminal's label and location the terminal's registration (before Face ID) on a swapped sticker the person sees the wrong machine's name
The sub-merchant's name the sub-merchant's registration the person knows whose money it is
description yours (≤ 80) — but never in notifications app text under the platform's title is a phishing channel

5. Before trusting a webhook

  • Verify the signature over the raw body (HMAC-SHA256(secret, t + "." + body), constant time), |now − t| ≤ 300 s.
  • Deduplicate on id; before an important step (dispensing, fulfilling an order) read the state again with GET.
  • The webhook address is public https; Gerege never delivers to private networks or loopback.

6. Claims and stickers

A terminal's code is printed, so it is not a secret. The protection is layered: an unconfirmed claim lapses after 90 s, each person holds at most 3 open claims, the merchant can unclaim, and the terminal's name comes from the registry before Face ID (Presentments and claims). NTAG 424 DNA (SUN) tags that give a signed URL on every tap are «удахгүй».

7. Personal data

You know a person only by a pseudonymous id; a merchant gets a new user_ref for every payment — Personal data.