Participant-scoped payment history
SubEtha can expose payment history through the Facilitator for a configured payer and provider. This is a participant-scoped API: a caller is not authorized merely because it knows a tenant or actor identifier. The record must bind that actor to the requested participant role, and the request must carry an authenticated principal.
The history API is an optional capability of a Facilitator deployment. It is not
available merely because the standard /verify or /settle endpoints are running.
HTTP endpoints
Section titled “HTTP endpoints”When the history API is registered, the Facilitator exposes these routes. The ordinary list/read routes require the complete history environment. The proof routes are attached as a group only when all proof issuer, trust-anchor, manifest, and durable artifact settings are present; partial proof configuration fails closed at startup.
| Method | Path | Purpose |
|---|---|---|
POST |
/history/auth/challenge |
Create a wallet-login challenge for payer or provider |
POST |
/history/auth/verify |
Verify the participant’s wallet signature and issue a short-lived identity token |
GET |
/history/payments |
List payments visible to the authenticated participant |
GET |
/history/payments/:paymentId |
Read one visible payment |
POST |
/history/proofs/issue |
Issue a proof when complete proof configuration is enabled |
POST |
/history/proofs/verify |
Verify a proof when complete proof configuration is enabled |
POST |
/history/proofs/revoke |
Revoke a proof when complete proof configuration is enabled |
The proof routes are unavailable unless the complete proof configuration (issuer, trust anchor, manifest, and durable artifact settings) is enabled; there is no verify-only or revoke-only deployment mode. With complete configuration, the official demo and standalone Facilitator daemon wire issue, offline-capable verification, and revoke through the same HTTP routes. No-proof-config deployments retain ordinary history list/read capability without proof publication. Deployments with no proof configuration should be described as no-proof-config deployments, not as deployments without History.
List/read requests carry the short-lived identity token as an
Authorization: Bearer token. GET /history/payments returns { payments: [...] };
pagination is
not part of this verified API surface, so consumers must not infer cursor,
limit, or ordering semantics.
The three proof routes also require an Authorization: Bearer token and resolve
the same authenticated participant identity. The route passes that identity and
the requested payment/proof identifier to the History API; authorization for
issuance, verification, and revocation is then enforced by the configured API
policy. The HTTP verify route is an online API call, even though the underlying
verifier can also be used locally with retained artifacts.
How offline verification works
Section titled “How offline verification works”This is the short version of the flow; the next section defines the verification states and boundaries precisely.
- Issue a signed proof. After a persisted payment reaches
acceptedorfinalized, the configured issuer creates a proof from that record and signs it. These states are not interchangeable:acceptedmeans the request path accepted the payment, whilefinalizedmeans the later proof-gated settlement completed. - Carry the issuer’s public key safely. The issuer publishes a signed key manifest. The verifier checks that manifest against an out-of-band bootstrap trust anchor before using the listed public key to check the proof signature.
- Keep the artifacts together. An operator-managed filesystem export can contain the proof, manifest, and signed status artifacts (for example,
proof.json,manifest.json, and a status chain). The current implementation does not provide a hosted registry or universal availability guarantee. - Verify locally. Once those artifacts and the trust anchor have been obtained, the verifier checks signatures, the proof’s internal payment-binding fields, validity times, and—when supplied—the signed status chain without calling the Facilitator or issuer API. Proof-only verification does not receive the canonical persisted record or salt, so it does not recompute the record commitment. Issuance itself may still require the configured Facilitator or another trusted issuer.
- Represent revocation separately. Revocation appends a signed status to the chain; it does not modify or delete the original proof bytes. The proof remains available as an issuance-time claim while the later status changes its current-status result.
This flow does not promise automatic long-term retention, replication, or protection against later copying of a bearer proof. Operators are responsible for protecting, backing up, rotating, and deleting filesystem exports according to their deployment requirements.
Offline verification after issuance
Section titled “Offline verification after issuance”An issued proof can be verified without calling the Facilitator or issuer API when the verifier has the signed proof and the issuer’s published key manifest. The manifest is validated against an out-of-band bootstrap trust anchor and supplies the issuer key registry used to verify the proof signature.
This is the outage-resilience boundary: if a payer or provider has already received
and retained the signed proof, the manifest, and the bootstrap trust anchor, that
party can later present and verify the issuer’s payment attestation without the
Facilitator being online. The attestation can establish the payment lifecycle state
recorded by the issuer—accepted for request-path acceptance or finalized for the
later proof-gated settlement—but it is not an unconditional replacement for chain
or operator evidence. Current-status claims additionally require the applicable
signed status artifact. Proofs and artifacts must be distributed and retained by
the participants or their operators in advance; the current implementation does
not provide automatic participant delivery, a hosted registry, universal
availability, or automatic retention.
The verification states are deliberately separate:
VALID_AT_ISSUANCE— the proof structure, signature, and issuance-time key validity verify; the proof carries a signed record-commitment value, but proof-only offline verification does not receive the canonical persisted record or salt and therefore cannot recompute that commitment.VALID_NOW— the proof is valid, the verified active status artifact is effective at the verification time, and the signing key is not revoked at that time.REVOKED— the proof is structurally valid and either a verified signed revocation artifact applies, or an otherwise valid active status is evaluated after the manifest’s issuer keyrevoked_athas taken effect. Later key metadata alone does not change the proof-only result.STATUS_UNKNOWN— a current-status artifact was supplied, but its provenance, signature, chain, or effective time cannot be verified. For an unexpired valid proof with no status artifact, the verifier returnsVALID_AT_ISSUANCEand makes no current-status claim; an expired proof returnsEXPIRED.EXPIRED— the proof’s validity window has ended.INVALID— the proof structure, signature, or manifest trust cannot be verified.
Evaluation is ordered by validity first: malformed or untrusted proof data is
INVALID; an expired proof is EXPIRED; an unexpired proof without a
status artifact is VALID_AT_ISSUANCE, while a supplied but unverifiable status
artifact is STATUS_UNKNOWN; VALID_NOW or REVOKED requires a verified
effective status or key-revocation condition.
Status artifacts are signed append-only publications bound to the proof, payment, issuer, and key. They change the status assertion, not the held proof itself. The verification path is offline after the artifacts have been obtained; proof issuance may still involve the configured Facilitator or another trusted issuer.
New lifecycle proofs are bound to a canonical persisted payment record through a per-record commitment. Caller-supplied commitments or evidence are not authoritative for this path. An issued proof remains a bearer artifact: participant authorization protects the online history API, but does not prevent later copying of an offline proof.
Wallet-bound authentication
Section titled “Wallet-bound authentication”The configured participant wallet signs the exact newline-delimited challenge
message returned by /history/auth/challenge using an EIP-191 personal-sign
(viem.verifyMessage) signature. The message contains:
- the history authentication purpose and domain;
tenantId,actorId, and the participant subject (payerorprovider);- a random nonce; and
- the challenge expiry.
The verified implementation uses these limits:
- challenge lifetime: 300 seconds;
- issued identity-token lifetime: at most 3600 seconds;
- per-client-IP-plus-subject challenge issuance limit: 10 per minute;
- per-client-IP-plus-subject verification limit: 20 per minute;
- pending challenge capacity: 10,000.
The rate-limit key uses the client IP derived from Express req.ip together with the
requested subject; deployments using a trusted proxy should configure that proxy
behavior deliberately because it affects the client-IP component of the key.
A challenge is single-use. A signature for another wallet, another subject, an expired
challenge, or a replayed nonce is rejected. The issued token carries the authenticated
participant identity; spoofed x-history-* headers are not an identity source in
wallet-token mode. A deployment may explicitly wire the legacy resolver, which
reads x-history-tenant and x-history-actor; that compatibility path is not
wallet authentication and should be limited to a trusted network.
Participant scope and projections
Section titled “Participant scope and projections”A configured history record binds the payer and provider independently, for example:
payer: tenant-a / payer-1provider: tenant-b / provider-1The payer can read the record only with the payer subject and matching payer binding. The provider can read it only with the provider subject and matching provider binding. An unrelated actor, a subject mismatch, or a record without a required participant binding fails closed in the configured History API, including the standalone path when its complete history environment is present.
The projections are role-specific:
- Payer: settlement linkage and operational secrets such as mint, treasury, and linkage fields are redacted. The payer view does not expose the real recipient destination through this history surface.
- Provider: the provider projection may include provider-side settlement evidence, subject to the deployment’s record and authorization configuration.
Authorization and participant binding are separate checks. A tenant grant alone does not establish that the actor participated in a particular payment.
Lifecycle and persistence
Section titled “Lifecycle and persistence”The Facilitator lifecycle adapter records an intent before an irreversible burn, then records accepted and finalized transitions as the settlement progresses. A failed history-intent write rolls back the in-memory reservation so a payment can be retried; it does not silently accept a payment without its required history intent.
The official demo attaches durable JSONL payment history to settlement when
PAYMENT_HISTORY_PATH is set. The standalone facilitator constructs and attaches
that persistence only when the complete History environment is present, including
PAYMENT_HISTORY_PATH, the identity secret, and both payer/provider tenant and
actor bindings. Neither path exposes the participant-aware HTTP API from the path
variable alone; the complete HTTP History configuration is required for that:
export PAYMENT_HISTORY_PATH=./payment-history.jsonlexport HISTORY_IDENTITY_SECRET='[REDACTED]'export HISTORY_PAYER_TENANT=tenant-aexport HISTORY_PAYER_ACTOR=payer-1export HISTORY_PROVIDER_TENANT=tenant-bexport HISTORY_PROVIDER_ACTOR=provider-1Wallet-bound HTTP login additionally requires HISTORY_PAYER_ADDRESS and
HISTORY_PROVIDER_ADDRESS. Keep the identity secret and wallet/private-key material
in the environment or a secret manager; never put real values in source, shell
history, or public documentation.
The verified persistence implementation is append-oriented and does not define an automatic record-retention period. Operators should choose file protection, backup, rotation, and deletion policies appropriate to the payment data they retain.
Relationship to payment settlement
Section titled “Relationship to payment settlement”History is an audit/read surface, not a replacement for the payment protocol:
acceptedmeans the payment was accepted on the request path;finalizedmeans the later proof-gated settlement completed;- a history record does not make
/settlesynchronous with finalization.
See accepted vs finalized and the Facilitator quickstart for the settlement flow.