# mTLS client certificates

The Payout server-to-server APIs are authenticated with an **mTLS client certificate** that you obtain from a Qualified Trust Service Provider (QTSP) and import into your Payout account. They currently cover:

- [**M2M Withdrawals**](https://developers.payout.tech/guides/m2m.html): withdrawals through Payout API v2, with optional [Verification of Payee](https://developers.payout.tech/guides/m2m.html#verification-of-payee-vop). Requires a QWAC for the mTLS connection **and** a QSEAL to sign payment instructions.

A certificate you imported once can be used by every server-to-server API whose profile requirements it meets.

## Certificate profiles

| Profile | What it is | Required for |
|---|---|---|
| **QWAC** (Qualified Website Authentication Certificate) | Establishes the mTLS connection. Issued by a QTSP, listed in the EU Trusted List. | All server-to-server APIs |
| **QSEAL** (Qualified Electronic Seal Certificate) | Signs payment instructions (detached JWS). Issued by a QTSP. | M2M Withdrawals only |

A standard eIDAS QWAC and QSEAL are enough: **no PSD2-specific extensions** are required, such as the QcStatement-PSD2 of ETSI TS 119 495 or PSP roles like `PSP_AS`, `PSP_PI`, `PSP_AI` and `PSP_IC`. Those are issued only to licensed payment service providers. M2M clients are typically merchants and other B2B companies, and they obtain plain qualified certificates from a supported QTSP.

Common requirements for all profiles:

- Issued by a QTSP on the [EU Trusted List](https://ec.europa.eu/tools/lotl/eu-lotl.xml). **Currently supported issuers: I.CA (První certifikační autorita) and Disig.** To use a certificate from another QTSP on the list, contact [tech@payout.one](mailto:tech@payout.one) and we will add its CA to our trust store.
- Contains your organisation identifier (IČO / LEI / VAT) in the `organizationIdentifier` subject attribute.
- Key usage matches the certificate's role:
  - QWAC: `digitalSignature` + `keyEncipherment`
  - QSEAL: `nonRepudiation`
- Minimum key size: RSA 2048 or ECDSA P-256.

Generate the private keys yourself and send only the CSR to the QTSP.

> [!WARNING]
> The private keys must never leave your infrastructure. Never send them to the QTSP or to Payout.

## Endpoints

Certificates are managed on the standard hosts, not on the mTLS hosts, because you import them **before** mTLS can work:

| Environment | Host |
|---|---|
| **Sandbox** | `https://sandbox.payout.one` |
| **Production** | `https://app.payout.one` |

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/api/v1/authorize` | [Get a bearer token](https://developers.payout.tech/api/payment.html#authorize_receive_api_token) with `client_id` and `client_secret` |
| `POST` | `/api/v1/mtls/certificates` | [Import a QWAC or QSEAL certificate](https://developers.payout.tech/api/payment.html#import_mtls_certificate) |
| `GET` | `/api/v1/mtls/certificates` | [List your imported certificates](https://developers.payout.tech/api/payment.html#list_mtls_certificates) |
| `GET` | `/api/v1/mtls/certificates/:thumbprint/status` | [Check the approval status](https://developers.payout.tech/api/payment.html#get_mtls_certificate_status) |
| `DELETE` | `/api/v1/mtls/certificates/:thumbprint` | [Remove a certificate](https://developers.payout.tech/api/payment.html#delete_mtls_certificate) |

## Setup

### 1. Obtain certificate(s) from a QTSP

Check in [Certificate profiles](#certificate-profiles) which profiles you need, then send a CSR to the QTSP of your choice.

### 2. Obtain a bearer token

```bash
curl -X POST https://app.payout.one/api/v1/authorize \
  -H "Content-Type: application/json" \
  -d '{"client_id":"<CLIENT_ID>","client_secret":"<CLIENT_SECRET>"}'
```

Response:

```json
{ "token": "...", "valid_for": 6000 }
```

### 3. Import your certificate(s)

Upload the PEM-encoded certificate. Send only the certificate, never the private key:

```bash
curl -X POST https://app.payout.one/api/v1/mtls/certificates \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "qwac",
    "pem": "-----BEGIN CERTIFICATE-----\n..."
  }'
```

For M2M Withdrawals, repeat for the QSEAL certificate with `"type": "qseal"`.

The response has status `201` and the certificate starts as `pending`:

```json
{
  "type": "qwac",
  "thumbprint": "49165a9f...",
  "status": "pending",
  "issuer_dn": "CN=...,O=...",
  "subject_dn": "C=SK,...,organizationIdentifier=NTRSK-12345678,CN=...",
  "subject_org_id": "NTRSK-12345678",
  "valid_from": "2025-06-09T06:23:06Z",
  "valid_until": "2026-06-09T06:23:06Z"
}
```

### 4. Wait for manual approval

Payout manually verifies that the imported certificates match the contractual data of your account. You can check the status at any time:

```bash
curl https://app.payout.one/api/v1/mtls/certificates/<thumbprint>/status \
  -H "Authorization: Bearer <TOKEN>"
```

After approval the status changes to `approved` and the certificate can be used. If Payout cannot approve it, the status changes to `rejected`.

### 5. Start calling the API

Once the certificate is `approved`, present it in the TLS handshake with the mTLS host of the API:

| API | mTLS host (sandbox) | mTLS host (production) |
|---|---|---|
| [M2M Withdrawals](https://developers.payout.tech/guides/m2m.html) | `api-mtls-sandbox.payout.one` | `api-mtls.payout.one` |

## Lifecycle

### Renewal

Before your certificate expires, obtain a new one from the QTSP and import it with `POST /api/v1/mtls/certificates`. The new certificate is approved separately and works in parallel with the old one until that one expires, so there is **no downtime**.

### Removal by you

You can remove your certificate at any time:

```bash
curl -X DELETE https://app.payout.one/api/v1/mtls/certificates/<thumbprint> \
  -H "Authorization: Bearer <TOKEN>"
```

### Rejection by Payout

Payout may also reject a certificate, for example when it has been compromised or the contractual relationship ends. A rejected certificate can no longer be used.

### Compromise

If you suspect your private key has been compromised:

1. Revoke the certificate at the QTSP that issued it.
2. Remove it from Payout (`DELETE`).
3. Notify Payout at [security@payout.one](mailto:security@payout.one).
4. Generate a new key pair and obtain a fresh certificate.
5. Import the new certificate and wait for re-approval.

## HTTP error codes

| Code | Meaning |
|---|---|
| `401 Unauthorized` | Missing or invalid bearer token. |
| `403 Forbidden` | At the mTLS host: the client certificate is missing, not `approved`, expired, or belongs to another account. |
| `404 Not Found` | No certificate with this thumbprint on your account. |
| `409 Conflict` | A certificate with the same thumbprint is already imported. |
| `422 Unprocessable Entity` | Malformed PEM, an issuer that is not a supported QTSP, or a missing or invalid field such as `type`. |
| TLS handshake refused | At the mTLS host: no client certificate, or one not issued by a trusted QTSP. |
