mTLS: us calling you
Step 1: Obtain root certificates
You can obtain the OV certificates to be added to you trust store here: https://support.globalsign.com/ca-certificates/intermediate-certificates/organizationssl-intermediate-certificates
You will need to concatenate these in one single file. For example:
cat ca1.pem ca2.pem ca3.pem > combined-ca-bundle.pemStep 2: (Optional) Example of webhook server doing client verification
// Pseudo code: mTLS setup
// 1. Create Cert Pool and append root CA
// 2. Enable Verification of the certificate
// 3. When we call your webhook endpoint
// 3a. You verify the presence of at least one valid peer certificate
// 3b. You verify if its DN matches the predefined communicated value (see below)
// DIGITEAL_DN_TEST = "CN=test.digiteal.eu,O=Digiteal SA,STREET=Rue Emile Francqui 6,L=Mont-Saint-Guibert,ST=Brabant wallon,C=BE,1.3.6.1.4.1.311.60.2.1.3=BE,SERIALNUMBER=0630.675.588,BusinessCategory=Private Organization,organizationIdentifier=VATBE-0630675588"
// DIGITEAL_DN_PROD "CN=prod.digiteal.eu,O=Digiteal SA,STREET=Rue Emile Francqui 6,L=Mont-Saint-Guibert,ST=Brabant wallon,C=BE,1.3.6.1.4.1.311.60.2.1.3=BE,SERIALNUMBER=0630.675.588,BusinessCategory=Private Organization,organizationIdentifier=VATBE-0630675588"
Create a file named webhook-server.go:
package main
import (
"crypto/tls"
"crypto/x509"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
const DIGITEAL_DN_TEST string = "CN=test.digiteal.eu,O=Digiteal SA,STREET=Rue Emile Francqui 6,L=Mont-Saint-Guibert,ST=Brabant wallon,C=BE,1.3.6.1.4.1.311.60.2.1.3=BE,SERIALNUMBER=0630.675.588,BusinessCategory=Private Organization,organizationIdentifier=VATBE-0630675588"
const DIGITEAL_DN_PROD string = "CN=prod.digiteal.eu,O=Digiteal SA,STREET=Rue Emile Francqui 6,L=Mont-Saint-Guibert,ST=Brabant wallon,C=BE,1.3.6.1.4.1.311.60.2.1.3=BE,SERIALNUMBER=0630.675.588,BusinessCategory=Private Organization,organizationIdentifier=VATBE-0630675588"
func webhookHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Received %s request from %s", r.Method, r.RemoteAddr)
// We verify if TLS is enabled and we have at least one valid peer certificate
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
w.WriteHeader(http.StatusUnauthorized)
io.WriteString(w, "mTLS authentication not provided")
return
}
// We verify that the Subject matches a pre-defined DN coming from Digiteal
cert := r.TLS.PeerCertificates[0]
if strings.Compare(DIGITEAL_DN_TEST, cert.Subject.String()) != 0 {
w.WriteHeader(http.StatusForbidden)
io.WriteString(w, "mTLS authentication failed")
return
}
w.WriteHeader(http.StatusOK)
io.WriteString(w, "Webhook received successfully\n")
}
func loadAndAppendCert(path string, certPool *x509.CertPool) {
cert, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Error loading cert from path %s: %v", path, err)
}
if !certPool.AppendCertsFromPEM(cert) {
log.Fatalf("Unable to add cert from path %s to cert pool", path)
}
}
// mTLS setup
// 1. Create Cert Pool and append root CA
// 2. Enable Verification of the certificate
// 3. When calling the /webhook endpoint - we will do client mTLS verification inside of the webhook handler
func main() {
// Configure TLS
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
}
// 1. Create Cert Pool and append root CA
caCertPool := x509.NewCertPool()
certPaths := "root-ca.pem"
loadAndAppendCert(certPaths, caCertPool)
// 2. Enable Verification of the certificate
tlsConfig.ClientCAs = caCertPool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
// 3. When calling the /webhook endpoint - we will do mTLS verification of the client inside of the webhook handler
handler := http.NewServeMux()
handler.HandleFunc("/webhook", webhookHandler)
// Configure the server
server := &http.Server{
Addr: ":8443",
Handler: handler,
TLSConfig: tlsConfig,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Println("Starting HTTPS webhook server on :8443")
log.Println("Endpoints:")
log.Println(" - https://localhost:8443/webhook")
log.Println(" - https://localhost:8443/health")
// Start the server with its server certs
if err := server.ListenAndServeTLS("server-bundle.crt", "server.key"); err != nil {
log.Fatalf("Server failed: %v", err)
}
}You can run the server:
# Install Go if not already installed
# Download from https://golang.org/dl/
# Run the webhook server
go run webhook-server.go3. Setup your webhook configuration
3.1 Activate mTLS on your webhooks at Digiteal
Contact support via https://support.digiteal.eu. We will get in touch with you to walk you through this process.
3.2 Add the url in the webhook config
If the mTLS port is different form the default port, please specify it in the request:
curl --request POST \
--url https://api-test.digiteal.eu/api/v1/webhook \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"type": "PEPPOL_INVOICE_RECEIVED",
"url": "https://api.mycompany.org:8443"
}
'4. End-to-end test
General troubleshooting
Certificate Issues
# Verify certificate is valid
openssl x509 -in server-bundle.crt -text -noout
# Check certificate expiration
openssl x509 -in server-bundle.crt -noout -enddate
# Test SSL connection
openssl s_client -connect webhook.yourcompany.com:8443 -servername webhook.yourcompany.comCommon Problems
| Issue | Solution |
|---|---|
| "x509: certificate signed by unknown authority" | Ensure you're using the bundled certificate with intermediate CA |
| "tls: private key does not match public key" | Regenerate CSR with the correct private key |
| Connection refused | Check firewall rules for port 8443 |
| Certificate name mismatch | Ensure CN in certificate matches your domain |
Certificate Renewal
⚠️ Important: OV SSL certificates typically expire after 1-2 years.
Set Renewal Reminder
# Check expiration date
openssl x509 -in server-bundle.crt -noout -enddate
# Add to calendar 30 days before expirationRenewal Process
- Generate new CSR (can reuse existing private key)
- Submit renewal request to your certificate authority
- Complete validation (usually faster for renewals)
- Replace certificates and restart server
Updated 22 days ago
