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.pem

Step 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.go

3. 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.com

Common Problems

IssueSolution
"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 refusedCheck firewall rules for port 8443
Certificate name mismatchEnsure 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 expiration

Renewal Process

  1. Generate new CSR (can reuse existing private key)
  2. Submit renewal request to your certificate authority
  3. Complete validation (usually faster for renewals)
  4. Replace certificates and restart server