mTLS for B2B API partners: a complete setup guide
Set up mTLS B2B partner API authentication in Zerq: certificate generation, nginx ingress config, profile setup, and per-partner audit trail in 7 steps.
- security
- mtls
- partner-integration
- api-management
- banking
- compliance
Most B2B API partner integrations start with a bearer token. You generate a secret, hand it to the partner, and both sides configure it in their systems. For a CRM connector, an analytics feed, or an internal service shared between subsidiaries, this is often enough.
For mTLS B2B partner API authentication in regulated industries — banking, payments, healthcare, government — a token alone is not sufficient. PSD3 and FAPI 2.0 require mutual TLS for payment initiation service providers and account information services. HIPAA business associate agreements increasingly demand channel-level machine identity for electronic protected health information APIs. A bearer token authenticates a credential. mTLS authenticates the machine that holds the private key. When a regulator asks how you know a request came from the authorised party rather than from a leaked credential, mTLS is the answer that bearer tokens cannot provide.
The technical challenge is not the certificate itself. It is the wiring: getting certificate identity to flow cleanly from the TLS handshake through your ingress, map to a partner record in your API gateway, and appear in the audit trail that compliance teams and regulators expect.
Why bearer tokens alone are not enough
Bearer tokens are single-factor credential authentication. If a partner's token leaks from a config file, a log line, or a compromised developer laptop, an attacker can present it from any IP at any time until you rotate it. The gateway has no way to distinguish a legitimate request from a replayed token.
mTLS changes the threat model. The partner's private key never leaves their infrastructure. Even if an attacker intercepts the credential or the TLS handshake metadata, they cannot authenticate without the private key. The two controls address different threat vectors, and for high-assurance flows, your security architecture should include both.
The harder problem is implementation. Most teams add mTLS at the load balancer, verify the certificate chain, and stop there. The gateway downstream does not know which partner the certificate belongs to. It receives requests that passed TLS but carry no partner identity context. Rate limits apply globally. Audit logs record source IPs but not partner names. There is no per-partner access control enforced at the gateway level.
Kong's mTLS plugin maps certificates to consumer records, but the mapping lives separately from your consumer definitions and requires route-level plugin configuration. Apigee handles mTLS via target server policies and XML configuration; connecting cert identity to API product access requires additional policy steps. AWS API Gateway's mutual TLS support is limited to a specific trust store configuration in the service itself, and mapping cert subject fields to authoriser logic requires a separate Lambda function. In each case, the certificate validates at the transport layer while the gateway's identity model is a separate concern — and that gap is where audit trails break and per-partner controls become hard to maintain.
How mTLS authentication works in Zerq
Zerq's mTLS profile type delegates certificate validation to the ingress layer and uses the certificate's subject field values to populate its standard identity headers: X-Client-ID and X-Profile-ID. The gateway then enforces the full access control model — method restrictions, IP allowlists, collection access, rate limits — using those headers, exactly as it would for a bearer token or JWT integration.
When the gateway receives a request with auth type mtls on the profile, it does not parse or verify the certificate itself. It trusts that the ingress has already validated the cert chain and reads identity from the forwarded headers. Certificate validation stays at the layer best equipped to handle it — the ingress, which has access to the live TLS session — while access control, rate limiting, and audit logging stay in the gateway, which holds the complete partner record.
Step-by-step: configuring mTLS for a high-assurance partner
The scenario: a licensed payment initiation service provider needs access to your /payments API collection. They authenticate with a client certificate issued by your internal CA. Requests must be restricted to GET and POST methods, limited to 500 requests per minute, and fully logged with partner identity in every entry.
Step 1 — Generate the partner's client certificate
Generate a certificate where the CN (Common Name) matches the client ID you will create in Zerq, and the OU (Organizational Unit) matches the profile ID. The ingress will extract these fields and inject them as the gateway identity headers.
# Generate client private key
openssl genrsa -out pisp-partner-acme.key 2048
# Generate CSR — CN becomes X-Client-ID, OU becomes X-Profile-ID
openssl req -new -key pisp-partner-acme.key -out pisp-partner-acme.csr \
-subj "/CN=pisp-partner-acme/OU=pisp-prod/O=Acme Payment Services/C=GB"
# Sign with your internal CA
openssl x509 -req \
-in pisp-partner-acme.csr \
-CA internal-ca.crt \
-CAkey internal-ca.key \
-CAcreateserial \
-out pisp-partner-acme.crt \
-days 365 \
-sha256
Send the partner pisp-partner-acme.crt and pisp-partner-acme.key. Keep internal-ca.crt on your ingress.
Step 2 — Configure your ingress to validate the certificate and forward identity headers
Configure nginx to require a valid client certificate signed by your CA, then extract the cert subject fields and forward them as Zerq identity headers. Requests without a valid certificate fail the TLS handshake at nginx and never reach the gateway.
server {
listen 443 ssl;
server_name gateway.example.com;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
# Require client certificate signed by your CA
ssl_client_certificate /etc/nginx/certs/internal-ca.crt;
ssl_verify_client on;
location / {
proxy_pass http://zerq-gateway:8080;
# Map cert subject fields to Zerq identity headers
proxy_set_header X-Client-ID $ssl_client_s_dn_cn; # CN → partner identity
proxy_set_header X-Profile-ID $ssl_client_s_dn_ou; # OU → active profile
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
}
$ssl_client_s_dn_cn and $ssl_client_s_dn_ou are nginx variables containing the certificate's Common Name and Organizational Unit fields respectively.
Step 3 — Create the partner client in Zerq
- Open Clients & Profiles → Clients in the Management UI.
- Click New client.
- Set:
- Name:
pisp-partner-acme(must exactly match the CN in the certificate) - Description:
Acme Payment Services — PISP license GB-PSD3-2890 - Status: Enabled
- Name:
- Click Save. Note the generated Client ID.
Step 4 — Create an mTLS profile
- Open Clients & Profiles → Profiles, click New profile.
- Set:
- Name:
pisp-prod(must exactly match the OU in the certificate) - Client:
pisp-partner-acme - Auth method:
mtls
- Name:
- Under Allowed methods, enable
GETandPOSTonly. - Click Save.
No bearer token is generated. Authentication is handled by the certificate at the ingress layer. The profile name (pisp-prod) is injected by nginx from the certificate's OU field on every request.
Step 5 — Attach a rate limit policy and assign collection access
- Open Clients & Profiles → Policies, click New policy:
- Name:
pisp-standard - Rate limit: 500 requests per minute
- Quota: 100,000 requests per day
- Name:
- Open the
pisp-partner-acmeclient, navigate to Policies, and attachpisp-standard. - Open the
paymentscollection, navigate to the Access tab, click Add client, selectpisp-partner-acme, and save.
Step 6 — Test the integration
Test from a machine that has the partner's certificate and private key:
curl -i https://gateway.example.com/payments/v1/charges \
--cert pisp-partner-acme.crt \
--key pisp-partner-acme.key \
-H "Accept: application/json"
Nginx validates the certificate chain, extracts CN=pisp-partner-acme and OU=pisp-prod, and injects them as X-Client-ID and X-Profile-ID. Zerq matches the client and profile records, applies the pisp-standard rate limit policy, confirms access to the payments collection, and routes the request.
| Scenario | Expected result |
|---|---|
| Valid cert from trusted CA | 200 |
| Missing client certificate | TLS handshake failure (400) |
| Certificate from untrusted CA | TLS handshake failure (400) |
Valid cert, DELETE method | 405 Method Not Allowed |
| Rate limit exceeded | 429 Too Many Requests |
Client not assigned to payments | 403 Forbidden |
Step 7 — Verify the audit trail
Open Logs → Request Logs and filter by Client ID = pisp-partner-acme. After the test call, you will see a structured entry like this:
{
"id": "req_01jq9vr4xs",
"method": "GET",
"path": "/payments/v1/charges",
"target_url": "https://payments-service.internal/v1/charges",
"latency": 38,
"status": 200,
"created_at": "2026-04-16T09:14:22Z",
"client_ip": "203.0.113.42",
"client_id": "pisp-partner-acme",
"profile_id": "pisp-prod",
"collection": "payments",
"request_id": "req_01jq9vr4xs"
}
Each field answers a specific compliance question:
client_id: which partner sent this request — required for per-PISP reporting under PSD3profile_id: which access profile was active — confirms the rate limit and method policy that appliedcollection: which API product was accessed — ties the request to your collection-level access grantstatus: the HTTP outcome —403entries show denied access attempts with full partner contextclient_ip: the source IP — useful for validating IP allowlists or detecting anomalies
For configuration change events — creating or modifying the profile, client, or policy — open Logs → Audit Logs. These entries record actor_id, actor_type, action, resource_type, and ip_address, giving your compliance team a complete record of who changed the partner configuration and when.
Adding IP allowlisting as a second control layer
For partners with stable outbound IP ranges, add an IP allowlist to the profile. If a certificate is presented from an unexpected IP — because the partner's infrastructure has moved, or because the certificate material has been compromised — the gateway returns 403 before routing to the backend, regardless of whether the certificate validates at the TLS layer.
- Open the
pisp-prodprofile. - Under IP allowlist, add the partner's egress IP ranges (for example,
203.0.113.0/24). - Save.
The certificate proves machine identity. The IP allowlist confirms the request came from the partner's expected infrastructure. Together they address the two most common attack scenarios: credential theft and cert exfiltration.
Certificate lifecycle and rotation
A 365-day certificate requires rotation before expiry. Issue renewal certificates with the same CN and OU values so the Zerq client and profile mapping continues to work without a gateway configuration change. If the partner's certificate is compromised before expiry, revoke it immediately via your CA's CRL or OCSP responder — nginx will reject the next handshake attempt from the compromised cert.
Track certificate issue dates using the last_rotated and token_expiry fields on the profile record. For mTLS profiles, these fields can hold the certificate issue date and planned expiry as an operational reminder. During rotation, issue the new certificate alongside the existing one, test that the new cert validates, then revoke the old cert — this overlap window avoids downtime during the handover.
What this looks like in a real open banking deployment
A retail bank integrating licensed PISPs under PSD3 had certificate validation at their WAF but no partner identity mapping at the gateway. Traffic passed TLS validation, but the gateway received requests with no partner context. Rate limits were global across all TPP traffic. Audit logs had source IPs but not partner identifiers. When the competent authority requested per-PISP access records, the compliance team reconstructed them from IP ranges and timing correlations — a process that took four weeks and produced results the authority questioned.
After configuring Zerq with the mTLS pattern above, each PISP has a dedicated client record and mtls profile. The nginx ingress maps certificate fields to X-Client-ID and X-Profile-ID. Rate limits apply per PISP. Every request log entry carries client_id and profile_id. The next open banking compliance audit produced a complete per-PISP access report by filtering request logs in the Management UI — in under an hour.
Twelve client records, twelve mTLS profiles, one ingress configuration update. The compliance capability that followed is ongoing.
Summary
mTLS for B2B API partners in Zerq gives you four things from a single configuration: channel-level trust through certificate validation at the ingress, per-partner access control through the profile system, rate limiting through the policy engine, and a complete audit trail with partner identity in every log entry. There is no custom plugin to write, no secondary authoriser to deploy, and no gap between the TLS layer and the gateway's identity model. The certificate validates at ingress. The gateway takes it from there.
Zerq is an enterprise API gateway built for regulated industries — one platform for API management, AI agent access, compliance audit, and developer portal, running entirely in your own infrastructure. See how it works or request a demo to walk through your specific requirements.