Mutual TLS authentication

The network traffic initiated by Dialogflow for webhook requests is sent on a public network. To ensure that traffic is both secure and trusted in both directions, Dialogflow optionally supports Mutual TLS authentication (mTLS). During Dialogflow's standard TLS handshake, your webhook server presents a certificate that can be validated by Dialogflow, either by following the Certificate Authority chain or by comparing the certificate to a Custom CA certificate. By enabling mTLS on your webhook server, Dialogflow will present its Google certificate to your webhook server for validation, completing the establishment of mutual trust.

Requesting mTLS

To request mTLS:

  1. Prepare your webhook HTTPS server to request the client certificate during the TLS handshake.
  2. Your webhook server should verify the client certificate upon receiving it.
  3. Install a certificate chain for your webhook server, which can be mutually trusted by both client and server. Applications connecting to Google services should trust all the Certificate Authorities listed by Google Trust Services. You can download root certs from:

Demo Node.js server

The following is a Node.js demo server:

  1. Prepare a self signed certificate and pem file.
  2. Prepare the certificate chain file:
    curl >> ca-crt.pem
  3. Save the following sample as server.js:
    var https = require('https');
    const express = require('express')
    var fs = require('fs');
    var options = {
      // Specify the key file for the server
      key: fs.readFileSync('./server-key.pem'),
      // Specify the certificate file
      cert: fs.readFileSync('./server-crt.pem'),
      // Specify the Certificate Authority certificate
      ca: fs.readFileSync('./ca-crt.pem'),
      // Requesting the client to provide a certificate, to authenticate the user.
      requestCert: true,
      // As specified as "true", so no unauthenticated traffic
      // will make it to the specified route specified
      rejectUnauthorized: false
    var app = express();
    app.use(function (req, res, next) {
      if (!req.client.authorized) {
        //return res.status(401).send('Client cert failed. User is not authorized\n');
      // Examine the cert itself, and even validate based on that!
      var cert = req.socket.getPeerCertificate();
      if (cert.subject) {
        console.log('Client Certificate: ',cert);
        console.log('Client Certificate Common Name: '+cert.subject.CN);
        console.log('Client Certificate Location: '+cert.subject.L);
        console.log('Client Certificate Organization Name: '+cert.subject.O);
        console.log('Client Certificate Email Address: '+cert.subject.emailAddress);
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end("hello world from client cert\n");
    var listener = https.createServer(options, app).listen(4433, function () {
      console.log('Express HTTPS server listening on port ' + listener.address().port);
  4. Run server.js.
  5. Set requestCert to be true.
    curl -k
  6. Send the client certificate. SSL handshake successfully completes.
    curl -v -s -k --key client-key.pem --cert client-crt.pem https://localhost:4433
    openssl s_client -key client-key.pem -cert client-crt.pem -connect localhost:4433 -CAfile ca-crt.pem

Best Practice

To make sure the webhook requests are initiated from your own Dialogflow agents, you should verify the Bearer service identity token from the request's Authorization header. Alternatively, you can verify the project ID in the resource names of the webhook requests (for example, session) in your webhook server.


If client certification fails (for example, no client certificate is sent from the client or the client certificate is not signed correctly), the TLS handshake fails and the session terminates.

Common error messages:

Error message Explanation
Failed to verify client's certificate: x509: certificate signed by unknown authority Dialogflow sends its client certificate to the external webhook, but the external webhook cannot verify it. This may be because the external webhook didn't install the CA chain correctly. All root CAs from Google should be trusted.