Architecture
This guide describes how the Pamodzi e-Services framework is put together: its components, the three roles an installation can run in, the path a request takes, where each kind of data is kept and the environments the platform runs in.
Components
| Component | Technology | Talks to |
|---|---|---|
National public portal (public-portal-ui) | React single-page application | The API of the installation it is served from |
Back office (back-office-ui) | React single-page application | The API of its own installation |
Platform API (zcm-backend) | Node.js, Express, Sequelize, PostgreSQL with PostGIS | Its database and storage; ZamPay, ZamPass, SMS and e-mail gateways, NGDR, register sources (all through an outbound allow-list); peer installations over /fed/v1 |
| Background workers | The same code base, job runner and queues | The same database |
POS app (GSB-POS) | Android | The API of its installation |
| Signing service | Part of the platform | Seals output documents and audit anchors |
Every installation is one copy of the same image. What differs between installations is configuration: environment variables and Helm values, never code branches.
The three platform roles
The setting PLATFORM_ROLE decides what an installation does.
| Role | What it is | Holds |
|---|---|---|
combined | Hub and node in one installation. The default, and how every installation runs today | Everything |
hub | The national portal. There is exactly one. It must scale horizontally | Citizen accounts, the catalogue copy, the node registry, index data about applications and payments |
node | One MDA's own back office, API and database | That MDA's services, cases, documents, payments and registers |
Each installation has a permanent name (NODE_KEY, for example hub or an MDA's short key) and its own ES256 signing key, published at /.well-known/jwks.json.
- A node trusts its hub from configuration (
HUB_URL,HUB_NODE_KEY, optionally pinned keys). - The hub trusts the nodes in its registry (System administration → Federation). Every change to the registry
needs a second administrator and is audited. Suspending a node takes effect at once.
The federation contract
Hub and nodes talk through the versioned /fed/v1 contract. Its OpenAPI file and JSON Schemas live in zcm-backend/docs/federation/, and the code reads the same files.
- Every message carries a detached JWS signature in the
X-GSB-Fed-Signatureheader, made with ES256 over thecanonical JSON body.
- The signature names the sender (
iss), the receiver (aud), the method and path, and a single-use id (jti). Amessage older than 5 minutes is refused, and a second delivery of the same message is refused as a replay.
- Answers are signed too, so a caller can tell a real answer from a forged one.
- The transport is mutual TLS with certificates from the federation certificate authority.
- Writes carry an Idempotency-Key, so a retried message is applied once.
In combined mode the same calls are signed and handed to the receiving code in-process (the loopback adapter). The same checks run, without a network hop. A change to a federation message is a contract change: the schema is updated, kept backward compatible (or hub and nodes ship together), and tested with two real instances.
Request paths
The paths below describe a service that lives on a node. In combined mode the same steps happen inside one installation.
Applying
- The citizen finds the service on the hub. The hub's catalogue is a copy of the node's signed service manifest,
received by federation sync.
- The citizen signs in on the hub and fills the mirrored form. Drafts are saved on the hub with a
HUB-reference. - Uploaded files are staged on the hub with the same checks as any upload: type by content, damage, password
protection, antivirus.
- On submit the hub checks the form's rules and required documents, then relays the application to the node
(
submissions.create) with a citizen assertion: a signed statement of who the citizen is and how strongly their identity was proved. - The node verifies the assertion, fetches the staged files through short-lived signed links, creates the case and
answers with its case number.
- Once the node acknowledges, the hub deletes the staged files. If the node is unreachable, the hub queues the
application and retries; after 7 days it expires, its staged files are deleted and the citizen is told.
- From then on the node sends status events (
case.submitted,case.status_changed,action.required). The hubkeeps only the index data needed for "My applications".
Paying
- The node raises the invoice on its own ZamPay merchant and revenue code, and tells the hub (
payment.invoiced). - The citizen sees the amount due on the hub. To pay, the hub asks the node, which answers with a short-lived signed
checkout link that leads to the node's own ZamPay checkout.
- ZamPay calls the node back. The node finalises the payment through its single finalisation path and sends
payment.settledto the hub. - The hub never holds a node's ZamPay keys, never marks a node's invoice paid and never keeps a copy of a receipt.
Verifying a document
- Anyone opens the verification page on the hub, usually by scanning the QR code on a document.
- The hub looks the code up in its own register first. A code that carries a node key goes to that node
(
verify.document). Other questions go to the nodes that serve the issuer, then to legacy verification services. - The answer says whether the document is genuine and whether it is still valid. Verification must never overstate
validity: expired, suspended and revoked documents say so.
The public verification page looks up codes only, never document numbers, and is rate-limited against guessing. Partner systems verify by document number through the Partner API with the permits:verify scope.
Where data lives
| Data | Kept on | Notes |
|---|---|---|
| Service design: forms, processes, fees, documents, templates | The owning node | The hub never becomes the place where a node's service is designed |
| Catalogue copy | Hub | Received by federation sync; only the passport and form needed to apply |
| Citizen account and sign-in | Hub | Nodes receive a pairwise subject and, when the rules allow, the NRC with its verification flag |
| Application answers, officer notes, uploaded documents | Node | Staged on the hub only until the node acknowledges |
| Application index: numbers, status words | Hub | For "My applications" |
| Invoices and receipts | Node | The hub keeps an index of amounts due, for display only |
| Issued documents | Node | The hub relays verification and never keeps a copy |
| National register records | The register's home installation | Read elsewhere only under a data-sharing agreement |
| Audit trail | Each installation, for its own actions | Both sides of a federated call audit it with the same federation id |
The rule is short: data stays with its owner. The hub keeps only the index data it needs: numbers, status words, amounts due and document labels.
Environments
| Environment | Where | Notes |
|---|---|---|
| Development | A developer's machine | Local PostgreSQL 18 with PostGIS. Integration tests create a fresh database per test file. Local tools are never pointed at a remote database |
| Test | eservices.pamodzi.co.zm (public portal) and admin.pamodzi.co.zm (back office) | Shared test deployment for demonstrations and product-owner review. Deployed only when the product owner asks. Never real citizen data. Uses the GSB test tenants of ZamPay and ZamPass |
| Staging | Government servers (GSB RKE2 cluster) | Same infrastructure as production. Images are promoted by digest through the image registry |
| Production | Government servers (GSB RKE2 cluster) | Changes reach it only by promotion from staging, with approvals. No direct deploys |
- Configuration differs per environment only through environment variables and Helm values.
- Test and staging use provider sandboxes or stubs until real credentials are issued.
- Production refuses development keys, such as the local signing key or the development certificate authority, and
refuses to start when a required secret is missing or holds a placeholder value.
Next steps
- Federation: the
/fed/v1contract in detail. - Environments: more on development, test, staging and production.
- Security and data protection: how requests are authorised.
- Registers and data sharing: how register data is shared across organisations and
nodes.