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_urlor the terminal'surl. - 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 withPOST /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'surland Gerege App'sgerege-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
requestPaymentanswer andreturn_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 withGET. - 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.