TPPVALIDATION
Talk to us

Your proxy asks. We answer in milliseconds.

A policy enforcement point beside your proxy, on the same point of presence as the handshake. Your F5, NGINX, Envoy or Kong keeps the TLS handshake and sends us the certificate it saw: one sideband call per new certificate, from the iRule, the Lua or the auth filter you already have. Your traffic path never moves.

200 today. 403 when you say so.

In observe every answer is 200: the verdict rides along as headers and a signed assertion, your own checks stay in charge. In enforce a QWAC that fails gets 403 from us and your proxy returns it. One switch per hostname, on your account page, in effect on the next request. Every decision leaves a signed, sealed and time-stamped record: the duly evidenced reason PSD2 Article 68 asks for when you refuse, ready for the PSR.

Under your own name. Only your proxy can call it.

A hostname on our zone, picked by you, or a name of your own: one TXT record proves it is yours, one CNAME brings it here, and the certificate for it is issued and renewed by us, never one for you to manage. Your proxy presents a bearer minted once, or its own client certificate, registered by fingerprint. Nothing else is answered.

  • 1rule in your proxy
  • 200 · 403the two answers
  • 3 msengine, warm
  • 0changes to your traffic path
  • ES256signed verdict, your key
  • EUone object per account

G1 · Observe or enforce

Second opinion today. Gatekeeper when you say so.

The Gatekeeper is a policy enforcement point at the edge: an edge-native, multi-tenant mTLS mesh beside your proxy, under your own names, answering on the same point of presence where the handshake happened. Your proxy keeps the TLS handshake and your traffic keeps its path. On a new certificate it makes one sideband call to us, from the iRule, the Lua or the auth filter you already have, and we answer in milliseconds. In observe the answer is always 200: the verdict rides along as headers and a signed assertion, your own checks stay in charge, ours are a second opinion. In enforce a QWAC that fails gets 403 from us and your proxy returns it. You flip the switch per hostname from your account page. It applies on the next request. No ticket, no redeploy, no DNS. Call it by your own name and the certificate for that name is ours to issue and renew: no certificate for you to manage, ever. Where that sits in your own drawing, in two views:

01

Observe

We always answer 200. The verdict travels on to your gateway as one header and one signed assertion; it reads them, logs them, compares them with its own truststore. Nothing is refused on our account. Start here: a second opinion with a full audit trail.

02

Enforce

A QWAC that is not qualified, not in a trusted list, revoked, or whose licence is withdrawn gets 403 from us, and your proxy returns it. Your API only ever sees traffic that passed.

03

The switch is yours

Per hostname, from your account page, in effect on the next request. Run a pilot in observe, read the headers for a week, then flip. Flip back just as fast. Your proxy rule never changes.

G2 · Four ways in

Your live traffic stays where it is.

No bank re-routes production traffic for a certificate check, and we do not ask you to. The Gatekeeper is something your proxy calls, something you run in your own edge, or something the TPP talks to before it reaches you. The only time we stand in a path is a sandbox you put us in. Same verdict, same headers, same switch.

Sideband · the usual way

Keep your proxy. Ask us.

Your F5, Citrix, NGINX, Envoy or Kong keeps the TLS handshake and sends us the certificate it saw: one rule, one call per new certificate. We answer in milliseconds with the verdict in headers and a signed assertion, or 403 when you have switched to enforce. Prefer to call it by your own name? One TXT record proves gk.bank.example is yours, one CNAME to connect.tppv.app brings it here, and the certificate for it is issued and renewed on our edge (Cloudflare for SaaS): you manage no certificate at all, and nothing expires on your watch. Your proxy authenticates with a bearer minted on your account page, or, on your name under tppv.app, with its own client certificate in the handshake. Nothing in your traffic path either way.

POST https://nordbank.tppv.app/_authz # or https://gk.bank.example/_authz Authorization: Bearer tppv_sb_… # or none: your proxy's client certificate in the handshake X-SSL-Client-Cert: <base64 DER> HTTP/2 200 · X-QWAC-Valid: true · X-QWAC-Assertion: eyJ…Envoy, NGINX, F5 and the raw contract
Self-hosted

The engine in your own edge

The same engine, deployed in your Cloudflare account, your VPC or an air gap, with the trusted lists ingested on your side. Speaks the sideband contract over HTTP, and ICAP REQMOD through a small adapter you run in your DMZ. Nothing of yours crosses the internet.

# the engine, deployed in your own Cloudflare account, VPC or DMZ tppv-icap --listen :1344 \ --upstream http://gatekeeper.internal/_authzLicensed with MSA and DPA
Ticket

The TPP asks us first

For an OAuth-shaped flow: the TPP does mTLS against auth.nordbank.tppv.app, we hold that handshake, verify the QWAC and issue a short-lived JWT bound to the certificate, RFC 8705 style. The TPP carries it to your API, and your API verifies one more JWT. Your traffic path is untouched.

POST https://auth.nordbank.tppv.app/ticket (mTLS) → { "access_token": "eyJhbGciOiJFUzI1NiIs…", "expires_in": 60 } GET https://api.bank.example/psd2/v1/accounts Authorization: Bearer eyJhbGciOiJFUzI1NiIs…What the token says
Sandbox · our zone

A path you choose to put us in

For a test environment or a pilot, a hostname on our zone can sit in front of your sandbox API: TPPs connect to it, we hold the handshake, and forward with the verdict attached. It is the one place we are in a path, and only because you put us there. Not for production, not by DNS.

https://nordbank.tppv.app/sandbox/v1/accounts X-QWAC-Valid: true X-QWAC-Roles: PSP_AI,PSP_PI X-QWAC-Assertion: eyJhbGciOiJFUzI1NiIs…Pick the hostname on your account page

G3 · Side by side

What changes, and what does not.

Way inYou changeWho holds the handshakeCan blockAdded timeThe verdict arrives as
SidebandOne rule in the proxy you run. Optionally one TXT record and one CNAME so the call goes to your own name.You do. We see the certificate it saw.403One sideband call per new certificate.200 or 403, headers and the assertion in the answer.
Self-hostedA deploy in your account or your DMZ.You do, or your copy of us.403Nothing crosses the internet.The same contract, on your network.
TicketYour API verifies one more JWT. The TPP adds one call.We do, with the TPP, before it reaches you.JWTOne call for the TPP. Nothing in your path.A certificate-bound JWT the TPP carries.
Sandbox on our zoneA base URL in your sandbox documentation.We do, in front of your test API.EnforceOne edge hop.Headers and the assertion on every request.

Observe never blocks, whichever way in. Enforce is one switch on your account page, per hostname. None of the four touch your production traffic path.

Where the verdict is appliedEdge PEP · sidebandICAP · adapter
Where it runsOn our edge, the same point of presence where the handshake happened. Multi-tenant, under your own name.In your DMZ, next to the load balancer, as a container you host.
What you changeOne rule in the proxy or API gateway you already run.An ICAP profile on the load balancer, and the adapter beside it.
Added timeMilliseconds: one HTTPS call per new certificate.One more hop inside your network, then the same call onward.
FitsAny proxy or gateway that can make a sideband call: that is nearly all of them.Load balancers whose policy only speaks ICAP.
The contract200 or 403, X-QWAC-* headers and the signed assertion.The same verdict, the same headers: the adapter speaks our sideband contract onward.

A policy enforcement point is where the verdict is applied. Ours is an edge-native, multi-tenant mTLS mesh beside your proxy, under your own names. ICAP is the same decision over an older transport, through an adapter you host.

G4 · Sideband, in your proxy's own words

One rule in the proxy you already run.

The sideband endpoint answers the way a proxy listens: 200 lets the request through, 403 stops it, nothing else. That is why a failed QWAC is 403 in enforce and 200 with the verdict attached in observe, while API v6, which answers a validation question, says 422. Same engine, same reason codes. The endpoint takes the certificate exactly as your proxy saw it and answers only to your proxy: the sideband bearer minted on your account page, or the client certificate you registered there by fingerprint. Anything else gets 401, on a hostname no TPP has a reason to call. That is what makes a forwarded header trustworthy: not the header, the caller. It is the same offload you already do with an iRule, a Lua hook or an auth filter. Pick your proxy.

curl -si -X POST "https://nordbank.tppv.app/_authz" \ -H "Authorization: Bearer $TPPV_BEARER" \ -H "X-SSL-Client-Cert: $(openssl x509 -in qwac.pem -outform DER | base64 -w0)" # the bearer (tppv_sb_…) is minted on your account page and shown once; we keep its hash. # or no bearer at all: --cert proxy.pem --key proxy.key, the proxy certificate registered # by its SHA-256 fingerprint on the account page, two at most so one can rotate. # On a name of your own by CNAME the bearer is what applies. HTTP/2 200 # 403 in enforce when the QWAC fails X-QWAC-Valid: true # the one your proxy branches on X-QWAC-Reason: # only when false: why X-QWAC-Assertion: eyJhbGciOiJFUzI1NiIsImtpZCI6Im5iLTIwMjYtMTAifQ… # the one your backend verifies # three headers are the contract. Optional, switched on per account when a gateway wants them: # X-QWAC-Roles, X-QWAC-Org-Id, X-QWAC-Country, X-QWAC-TSP, X-TPP-Passports # also accepted on the way in: X-Forwarded-Client-Cert (Envoy XFCC) or a PEM body

G5 · One verdict, signed

Headers for your gateway. A signature for your auditors.

Our answer reaches your proxy over TLS with nothing in between: there, a header is ours. It is the hops after that, inside your own perimeter, where a header is just a header. So every verdict also travels as a compact JWT, signed with a key that belongs to your account alone. Your API verifies it against a JWKS on your own hostname, the way it already verifies any token, and years later it still proves what we saw. Headers for routing, the assertion for trust.

The assertion

What the token says

Issued per request, short-lived, bound to the certificate's fingerprint. The claims are the verdict and its sources: the trusted list and its sequence, the revocation answer, the register record's hash. An auditor traces it from the token alone; the audit on your account page does exactly that.

{ "iss": "https://nordbank.tppv.app", // your own name once it is live "aud": "psd2-api", "iat": 1792742400, "exp": 1792742460, "jti": "01JB…", "sub": "PSDSE-FINA-556000-0000", "qwac": { "valid": true, "indication": "PASSED", "type": "QWAC", "orgId": "PSDSE-FINA-556000-0000", "country": "SE", "serial": "4f3a…", "roles": ["PSP_AI","PSP_PI"], "tsp": "Netcetera Software Services GmbH", "register": true, "passports": ["SE","DK"] }, "tl": { "territory": "SE", "seq": 142 }, "rev": { "status": "good", "source": "ocsp", "at": "…", "until": "…" }, "reg": { "ok": true, "hash": "7c1f…", "at": "…", "passports": ["SE","DK"] }, "cnf": { "x5t#S256": "9f2c…" } }
Verifying it

Any gateway that reads a JWKS

Point your JWT filter at the JWKS on your hostname. When you rotate the key from your account page, your gateway picks the new one up by itself.

# Envoy jwt_authn providers: tppv: issuer: https://nordbank.tppv.app audiences: [ psd2-api ] remote_jwks: http_uri: { uri: https://nordbank.tppv.app/.well-known/jwks.json, cluster: tppv, timeout: 1s } cache_duration: 300s from_headers: [ { name: X-QWAC-Assertion } ]Audience, lifetime and rotation on your account page
ES

Asymmetric only, one object per account

ES256 on P-256. We never hand out a shared secret: the private key is made and kept in a store that belongs to your account alone, in the EU, and signs without ever leaving it. Rotate from your account page; the previous key stays in the JWKS for an hour, so a gateway that cached it keeps verifying. Nothing you store can mint a verdict, and nothing of yours sits in a table next to another bank's.

TK

The same token as a ticket

In the ticket flow the TPP fetches exactly this JWT from auth.nordbank.tppv.app after an mTLS handshake with us, and carries it to your API. Your API verifies the token and the certificate fingerprint in cnf. The same key, the same JWKS.

G6 · From your account

Your settings, not our tickets.

Everything the Gatekeeper does for you is set on your account page, by you, in effect on the next request.

01

Mode, per hostname

Observe or enforce. The one switch this page is about.

02

Your hostname

Your name on tppv.app, picked by you, with a ping that shows it answering. It is where your proxy calls, where your JWKS lives, where the TPP fetches a ticket, and where a sandbox can sit. Never your production traffic path.

03

Your own name

A subdomain you govern, gk.bank.example, in three steps you watch on the page: a TXT record at _tppv.gk.bank.example with the token we show you, which proves the name is yours before anyone can claim it; a CNAME to connect.tppv.app; and the certificate we issue on it. Once both are active, the JWKS and the issuer of every signed verdict move to your name.

04

Sideband credential

What your proxy presents, whichever name it calls. A bearer, minted once and shown once, kept by us only as its hash; or your proxy's own client certificate, registered by SHA-256 fingerprint or pasted as PEM, two at most so one can rotate. With the certificate in the handshake the call needs no bearer. One is enough; both stay valid if you set both.

05

Origin and passports

Where we forward, and which jurisdictions the licence must cover. The register check runs against exactly those.

06

Signed verdict

Audience, lifetime, the JWKS URL to paste, the current kid, and a rotate button.

07

Audit

Paste an assertion from your gateway log, or a v6 answer. The signature is checked against your key, and the verdict is traced to the trusted list, the revocation answer and the EBA record, stamped, and printed as a sheet.

G7 · Plain facts

What we will not pretend.

We never rely on the platform's CAThe edge hands us the certificate whether or not it chains to a CA it knows. The verdict is ours: 31 trusted lists, OCSP or CRL, ETSI TS 119 615, the EBA register.
Observe never blocksNot on a timeout, not on an outage, not on a bug. In observe the worst case is a missing header.
Sideband trusts your handshakeWe see the certificate your proxy saw. Proof of possession is your proxy's, and it already does it well. If you want ours too, the ticket flow or the challenge on the API v6.
ICAP is an adapter you hostICAP is raw TCP; nothing on our edge listens for it. The adapter runs in your DMZ, next to the proxy, and speaks the sideband contract to us or to your self-hosted engine.
Certificates up to 10 KBWhat the platform forwards from the handshake. Every QWAC we have seen fits; a chain that does not is reported, not guessed.
We do not want your live trafficNo re-routing, no change to where your TPPs connect. Your proxy calls us. A CNAME to your sideband hostname is a name, not a path. The only path we ever stand in is a sandbox you put us in.
A verdict is evidence, not a headerSigned with your account's key, with the trusted list, the revocation answer and the register hash inside, and kept as a record at the call. What a bank shows for a reject under PSD2 Article 68 is in the token, not in a log you have to reconstruct. The record behind it is sealed into your chain and anchored with rFC 3161 qualified electronic time-stamps issued by AlfaTrust Certification S.A., a Qualified Trust Service Provider on the EU Trusted Lists: legally recognised in every Member State under eIDAS Article 41, verifiable with the EU DSS validator.
A name is proven, not assumedYour own name is claimed only after the TXT record under it carries the token we made for your account. No account can reserve a name that belongs to another bank, and no certificate is issued before that.