What is webhook mTLS?
Webhook mTLS (mutual TLS) is a way of locking a webhook endpoint so that only Meta can connect to it. In ordinary TLS only the server proves its identity; in mutual TLS the server also asks the client for a certificate during the handshake and refuses the connection unless that certificate chains to a trusted certificate authority and names the expected client. For WhatsApp webhooks the client is Meta's webhook infrastructure and the server is your endpoint.
Meta presents a client certificate with the common name client.webhooks.fbclientcerts.com, together with its signing intermediate. That certificate is now signed by a Meta-owned certificate authority: Meta publishes the CA file meta-outbound-api-ca-2025-12.pem for your trust store, replacing the previous DigiCert-signed certificate, which expired on 15 April 2026. Your server, or the load balancer in front of it, verifies the chain against that CA and then checks the common name — trusting the CA alone is not enough.
Meta's WhatsApp documentation suggests mTLS instead of allowlisting its IP addresses, which change periodically. Once mTLS is enabled on your WhatsApp Business Account webhook subscription, Meta presents the client certificate on every webhook request to your endpoint, and the check itself runs on your side — in your web server or load balancer. It complements X-Hub-Signature-256 rather than replacing it: every Meta app's webhooks arrive with a certificate carrying the same common name, so mTLS proves the connection came from Meta but cannot tell your app's deliveries from another app's — only the HMAC signature keyed with your app secret can.
Key takeaways
- Webhook mTLS makes your endpoint demand a client certificate, so a connection that is not from Meta fails during the TLS handshake.
- Trust Meta's CA file meta-outbound-api-ca-2025-12.pem and check the common name client.webhooks.fbclientcerts.com.
- Meta's webhook client certificate now comes from a Meta-owned CA; the earlier DigiCert-signed one expired on 15 April 2026.
- Meta suggests mTLS instead of IP allowlists because the IP addresses its webhooks come from change periodically.
- mTLS authenticates the connection, not the payload: keep verifying X-Hub-Signature-256 on every POST.
Where does the mTLS check happen?
Stage 5 of 5 · Deliver
The recipient decrypts hop 2. Encrypted on every hop, yet readable in the middle.
At Your webhook app:messages: Is my order shipped?
Meta webhook serverssees
Serversees
Your webhook appsees
Can read the message
- Meta's webhook infrastructure
- Your load balancer, after verifying Meta's client certificate
- Your webhook app
Cannot read it
- Anyone watching the network on either hop
- Proxies and Wi-Fi along the path
Step 5 of 5, Deliver: The recipient decrypts hop 2. Encrypted on every hop, yet readable in the middle.
How does webhook mTLS work?
Download Meta's CA certificate
Get
meta-outbound-api-ca-2025-12.pemfrom Meta's webhooks documentation and add it to the trust store your TLS terminator uses for client certificates.Require a client certificate
Configure the endpoint to request and verify client certificates against that CA — in Nginx,
ssl_verify_client onwithssl_client_certificate; on a managed load balancer, an mTLS listener with a trust store.Check the common name
Accept only the subject
CN=client.webhooks.fbclientcerts.com. In Nginx compare$ssl_client_s_dn; behind a load balancer, read the subject header it forwards, such asX-Amzn-Mtls-Clientcert-Subject.Keep the signature check
In the application, still verify
X-Hub-Signature-256over the raw body with your app secret before processing anything.Plan for CA changes
Meta announces new client-certificate authorities in advance. Add a new CA to the trust store while the old one is still valid, so the switch does not drop webhooks.
Webhook mTLS in code
server {listen 443 ssl;server_name wa-webhooks.example.com; # dedicated hostname for webhooksssl_certificate /etc/ssl/webhooks/fullchain.pem;ssl_certificate_key /etc/ssl/webhooks/privkey.pem;# Mutual TLS: require and verify Meta's client certificatessl_verify_client on;ssl_client_certificate /etc/ssl/certs/meta-outbound-api-ca-2025-12.pem;ssl_verify_depth 3;location /webhooks/whatsapp {# Trusting the CA is not enough: pin the client identity tooif ($ssl_client_s_dn !~ "(^|,)CN=client\.webhooks\.fbclientcerts\.com(,|$)") {return 403;}proxy_pass http://127.0.0.1:3000;}}
Nginx answers clients without a certificate with HTTP 400 "No required SSL certificate was sent".
Why does webhook mTLS matter?
A public webhook URL receives traffic from anyone who finds it: scanners, fuzzers, attackers replaying captured payloads. Signature checks reject forged events, but only after your application has accepted the connection, read up to 3 MB of body and computed an HMAC. mTLS moves the rejection to the edge — clients without Meta's certificate never reach your code. For regulated businesses it also gives auditors a clear network-level control that, unlike an IP allowlist, does not break when Meta's address ranges change.
Key facts at a glance
Webhook mTLS: key facts and limits
- Client certificate CN
- client.webhooks.fbclientcerts.com
- CA to trust
- meta-outbound-api-ca-2025-12.pem (Meta-owned CA)
- Previous certificate
- DigiCert-signed, expired 15 April 2026
- Where you check the CN
- Nginx $ssl_client_s_dn or ALB X-Amzn-Mtls-Clientcert-Subject
- Meta's suggestion
- mTLS instead of IP allowlists (IPs change)
- Still required
- X-Hub-Signature-256 check on every POST
Common wrong turns
Common mistakes with webhook mTLS
Trusting the CA but not checking the name
Verifying the chain only proves the certificate came from Meta's CA. Pin the subject to
CN=client.webhooks.fbclientcerts.com, as Meta's instructions say.Dropping the signature check
Every Meta app's webhooks present a certificate with the same common name, so mTLS cannot tell your app's deliveries from anyone else's. Only
X-Hub-Signature-256, keyed with your app secret, proves the event is yours and unaltered.Missing a CA change
When Meta moved off the DigiCert-signed certificate, an endpoint that trusted only the old chain would reject Meta's new certificate at the handshake and stop receiving webhooks. Follow Meta's developer changelog and add new CAs early.
Enforcing mTLS on a shared hostname
ssl_verify_client onapplies to every path in that server block, including health checks and other integrations. Give the webhook its own hostname or listener.
Example: Locking down a lender's webhook
A Jaipur microfinance lender's security audit flags its WhatsApp webhook URL as reachable by anyone on the internet. The team moves it to a dedicated hostname behind its load balancer, uploads Meta's CA file to an mTLS trust store and forwards the verified certificate subject to the app, which rejects anything other than CN=client.webhooks.fbclientcerts.com before checking X-Hub-Signature-256. Scanners now fail at the handshake and never reach the application, the auditor records a network-level control, and the IP allowlist the team had been maintaining by hand is retired.
Good practice
Best practices for webhook mTLS
- Roll out with
ssl_verify_client optionaland log$ssl_client_verifyfirst, then switch toon. - Alert on client-certificate failures at the load balancer — a spike after a Meta certificate change is the first sign of trouble.
- Leave IP allowlists out of new designs; Meta changes its addresses.
- Record the CA file name in your runbook so the next certificate change is a known task.