Before you begin
- Obtain the dedicated mTLS API hostname from your platform operator. The normal web ingress does not forward client certificates.
- Create the private key on the client that will use it. Submit only the public certificate in Customer API integration management.
- Confirm the certificate includes client-authentication usage and has not expired or been revoked.
Never upload or send the private key. Wavac needs only the public client certificate.
Create and register a certificate
These examples create the same encrypted 3072-bit RSA private key and certificate signing request. Run the example for your operating system from a protected working directory.
Linux: Bash and OpenSSL
# Create an encrypted private key. OpenSSL prompts for its passphrase.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
-aes-256-cbc -out customer-api-client.key
# Create the CSR that your approved CA will sign for client authentication.
openssl req -new -key customer-api-client.key \
-out customer-api-client.csr -subj "/CN=your-integration-name"
Windows: PowerShell and OpenSSL
# Run in PowerShell. Install OpenSSL first if it is not already available.
# Create an encrypted private key; OpenSSL prompts for its passphrase.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 `
-aes-256-cbc -out customer-api-client.key
# Create the CSR that your approved CA will sign for client authentication.
openssl req -new -key customer-api-client.key `
-out customer-api-client.csr -subj "/CN=your-integration-name"
- Have your approved certificate authority sign the CSR for TLS client authentication. Keep the returned certificate chain with the client.
- In Dashboard Customer API settings, add or renew the mTLS method and paste the PEM public certificate. Confirm its subject, expiry, and CRL distribution point before saving.
- Use discovery to verify the effective site and line scope.
Package the signed certificate for .NET
After the CA returns customer-api-client.crt, package it with the matching private key for the .NET example below. OpenSSL prompts for a new export password that protects the PFX.
Linux: Bash and OpenSSL
# Combine the signed public certificate and private key into an encrypted PFX.
openssl pkcs12 -export \
-out customer-api-client.pfx \
-inkey customer-api-client.key \
-in customer-api-client.crt
Windows: PowerShell and OpenSSL
# Combine the signed public certificate and private key into an encrypted PFX.
openssl pkcs12 -export `
-out customer-api-client.pfx `
-inkey customer-api-client.key `
-in customer-api-client.crt
Call discovery with the certificate
Linux: Bash and curl
# Keep the hostname configurable; use the dedicated mTLS hostname from Wavac.
export IOTSNAP_MTLS_URL='https://your-mtls-api-host'
# Present the public certificate and matching private key during TLS setup.
curl --fail-with-body \
--cert ./customer-api-client.crt \
--key ./customer-api-client.key \
"$IOTSNAP_MTLS_URL/api/customer/v1/discovery"
Windows: PowerShell and curl.exe
# Set a process-only environment variable for the dedicated mTLS hostname.
$env:IOTSNAP_MTLS_URL = 'https://your-mtls-api-host'
# Use curl.exe explicitly so PowerShell does not substitute a web-command alias.
# Present the public certificate and matching private key during TLS setup.
curl.exe --fail-with-body `
--cert .\customer-api-client.crt `
--key .\customer-api-client.key `
"$env:IOTSNAP_MTLS_URL/api/customer/v1/discovery"
If the private key is encrypted, curl prompts for its passphrase. Prefer a protected agent or operating-system certificate store for unattended workloads.
Runnable .NET example
The C# client is cross-platform. Set the three environment variables in Bash or PowerShell, then place this commented example in Program.cs.
// Load X.509 support from the .NET base class library.
using System.Security.Cryptography.X509Certificates;
// Read deployment-specific values instead of placing certificate data in source.
var baseUrl = Environment.GetEnvironmentVariable("IOTSNAP_MTLS_URL")
?? throw new InvalidOperationException("Set IOTSNAP_MTLS_URL.");
var certificatePath = Environment.GetEnvironmentVariable("IOTSNAP_CLIENT_PFX")
?? throw new InvalidOperationException("Set IOTSNAP_CLIENT_PFX.");
var certificatePassword = Environment.GetEnvironmentVariable("IOTSNAP_CLIENT_PFX_PASSWORD");
// Load the PFX and attach it to every TLS request created by this handler.
using var certificate = X509CertificateLoader.LoadPkcs12FromFile(
certificatePath, certificatePassword);
using var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
using var client = new HttpClient(handler) { BaseAddress = new Uri(baseUrl) };
// Call discovery first and fail clearly when the API rejects the certificate.
using var response = await client.GetAsync("/api/customer/v1/discovery");
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
response.EnsureSuccessStatusCode();
Linux: Run with Bash
# Create the console project once and replace Program.cs with the example above.
dotnet new console --name CustomerApiMtls
cd CustomerApiMtls
# Configure the endpoint and PFX location only for this shell session.
export IOTSNAP_MTLS_URL='https://your-mtls-api-host'
export IOTSNAP_CLIENT_PFX='./customer-api-client.pfx'
# Prompt without echoing the PFX password, run, and then clear it.
read -rsp 'PFX password: ' IOTSNAP_CLIENT_PFX_PASSWORD && echo
export IOTSNAP_CLIENT_PFX_PASSWORD
dotnet run
unset IOTSNAP_CLIENT_PFX_PASSWORD
Windows: Run with PowerShell
# Create the console project once and replace Program.cs with the example above.
dotnet new console --name CustomerApiMtls
Set-Location CustomerApiMtls
# Configure the endpoint and PFX location only for this PowerShell process.
$env:IOTSNAP_MTLS_URL = 'https://your-mtls-api-host'
$env:IOTSNAP_CLIENT_PFX = '.\customer-api-client.pfx'
# Prompt without echoing the PFX password, run, and then clear it.
$password = Read-Host 'PFX password' -AsSecureString
$env:IOTSNAP_CLIENT_PFX_PASSWORD = `
[System.Net.NetworkCredential]::new('', $password).Password
dotnet run
$env:IOTSNAP_CLIENT_PFX_PASSWORD = $null
$password.Dispose()
Runnable Python example
Python can use the PEM certificate and encrypted private key created earlier, so it does not need the PFX used by .NET. Save this commented example as customer_api_mtls.py.
# Import only Python standard-library modules; no package installation is required.
import os
import ssl
import urllib.request
# Fail clearly when required process configuration is missing.
def required(name: str) -> str:
value = os.environ.get(name)
if not value:
raise RuntimeError(f"Set {name}.")
return value
# Read deployment-specific values instead of placing certificate data in source.
base_url = required("IOTSNAP_MTLS_URL").rstrip("/")
certificate_path = required("IOTSNAP_CLIENT_CERT")
private_key_path = required("IOTSNAP_CLIENT_KEY")
private_key_password = os.environ.get("IOTSNAP_CLIENT_KEY_PASSWORD")
# Start with the operating system's trusted server-certificate authorities.
tls_context = ssl.create_default_context()
# Present the registered client certificate and matching private key during TLS.
tls_context.load_cert_chain(
certfile=certificate_path,
keyfile=private_key_path,
password=private_key_password,
)
# Call discovery first and require a successful HTTP response.
request = urllib.request.Request(
f"{base_url}/api/customer/v1/discovery",
headers={"Accept": "application/json"},
)
with urllib.request.urlopen(request, context=tls_context, timeout=30) as response:
print(response.read().decode("utf-8"))
Linux: Run Python with Bash
# Configure the endpoint and PEM paths only for this shell session.
export IOTSNAP_MTLS_URL='https://your-mtls-api-host'
export IOTSNAP_CLIENT_CERT='./customer-api-client.crt'
export IOTSNAP_CLIENT_KEY='./customer-api-client.key'
# Prompt without echoing the private-key password, run, and then clear it.
read -rsp 'Private-key password: ' IOTSNAP_CLIENT_KEY_PASSWORD && echo
export IOTSNAP_CLIENT_KEY_PASSWORD
python3 customer_api_mtls.py
unset IOTSNAP_CLIENT_KEY_PASSWORD
Windows: Run Python with PowerShell
# Configure the endpoint and PEM paths only for this PowerShell process.
$env:IOTSNAP_MTLS_URL = 'https://your-mtls-api-host'
$env:IOTSNAP_CLIENT_CERT = '.\customer-api-client.crt'
$env:IOTSNAP_CLIENT_KEY = '.\customer-api-client.key'
# Prompt without echoing the private-key password, run, and then clear it.
$password = Read-Host 'Private-key password' -AsSecureString
$env:IOTSNAP_CLIENT_KEY_PASSWORD = `
[System.Net.NetworkCredential]::new('', $password).Password
py customer_api_mtls.py
$env:IOTSNAP_CLIENT_KEY_PASSWORD = $null
$password.Dispose()