Servicio de webhooks

Para usar entregas en un sistema de producción, debes implementar y aplicar un servicio de webhook. Para administrar la entrega, el servicio webhook debe aceptar solicitudes JSON y mostrar respuestas JSON como se especifica en esta guía. El flujo de procesamiento detallado para las entregas y los webhooks se describe en el documento de descripción general de las entregas.

Requisitos del servicio de webhook

El servicio de webhook debe cumplir con los siguientes requisitos:

  • Debe administrar solicitudes HTTPS. HTTP no es compatible. Si alojas tu servicio de webhook en Google Cloud Platform mediante una solución de Compute o de procesamiento sin servidores, consulta la documentación del producto para la entrega con HTTPS. A fin de conocer otras opciones de hosting, consulta Obtén un certificado SSL para el dominio.
  • La URL para las solicitudes debe ser de acceso público.
  • Debe administrar las solicitudes POST con un cuerpo JSON WebhookRequest.
  • Debe responder a las solicitudes WebhookRequest con un cuerpo JSON WebhookResponse.

Autenticación

Es importante proteger el servicio de webhook para que solo tú o tu agente de Dialogflow estén autorizados a realizar solicitudes. Dialogflow admite los siguientes mecanismos de autenticación:

Solicitud de webhook

Cuando hay una coincidencia con un intent configurado para la entrega, Dialogflow envía una solicitud POST HTTPS del webhook al servicio de webhook. El cuerpo de esta solicitud es un objeto JSON con información sobre el intent que coincide.

Además de la consulta del usuario final, muchas integraciones también envían información sobre este. Por ejemplo, un ID para identificar de forma única al usuario. Se puede acceder a esta información a través del campo originalDetectIntentRequest en la solicitud de webhook, que contendrá la información enviada desde la plataforma de integración.

Consulta la documentación de referencia de WebhookRequest para obtener más información.

A continuación, se muestra una solicitud de ejemplo:

{
  "responseId": "response-id",
  "session": "projects/project-id/agent/sessions/session-id",
  "queryResult": {
    "queryText": "End-user expression",
    "parameters": {
      "param-name": "param-value"
    },
    "allRequiredParamsPresent": true,
    "fulfillmentText": "Response configured for matched intent",
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            "Response configured for matched intent"
          ]
        }
      }
    ],
    "outputContexts": [
      {
        "name": "projects/project-id/agent/sessions/session-id/contexts/context-name",
        "lifespanCount": 5,
        "parameters": {
          "param-name": "param-value"
        }
      }
    ],
    "intent": {
      "name": "projects/project-id/agent/intents/intent-id",
      "displayName": "matched-intent-name"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": "en"
  },
  "originalDetectIntentRequest": {}
}

Respuesta de webhook

Una vez que tu webhook reciba una solicitud, deberá enviar una respuesta de webhook. El cuerpo de esta respuesta es un objeto JSON con la siguiente información:

Se aplican las siguientes limitaciones a tu respuesta:

  • La respuesta debe ocurrir en un plazo de 10 segundos para las aplicaciones del Asistente de Google o de 5 segundos para todas las otras aplicaciones; de lo contrario, se agotará el tiempo de espera de la solicitud.
  • La respuesta debe tener un tamaño menor o igual que 64 KiB.

Consulta la documentación de referencia de WebhookResponse para obtener más información.

Respuesta de texto

Ejemplo de una respuesta de texto:

{
  "fulfillmentMessages": [
    {
      "text": {
        "text": [
          "Text response from webhook"
        ]
      }
    }
  ]
}

Respuesta de tarjeta

Ejemplo de una respuesta de tarjeta:

{
  "fulfillmentMessages": [
    {
      "card": {
        "title": "card title",
        "subtitle": "card text",
        "imageUri": "https://example.com/images/example.png",
        "buttons": [
          {
            "text": "button text",
            "postback": "https://example.com/path/for/end-user/to/follow"
          }
        ]
      }
    }
  ]
}

Respuesta del Asistente de Google

Ejemplo de una respuesta del Asistente de Google:

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "this is a Google Assistant response"
            }
          }
        ]
      }
    }
  }
}

Context

Ejemplo que establece el contexto de salida:

{
  "fulfillmentMessages": [
    {
      "text": {
        "text": [
          "Text response from webhook"
        ]
      }
    }
  ],
  "outputContexts": [
    {
      "name": "projects/project-id/agent/sessions/session-id/contexts/context-name",
      "lifespanCount": 5,
      "parameters": {
        "param-name": "param-value"
      }
    }
  ]
}

Evento

Ejemplo que invoca un evento personalizado:

{
  "followupEventInput": {
    "name": "event-name",
    "languageCode": "en-US",
    "parameters": {
      "param-name": "param-value"
    }
  }
}

Entidad de sesión

Ejemplo que establece una entidad de sesión:

{
  "fulfillmentMessages": [
    {
      "text": {
        "text": [
          "Choose apple or orange"
        ]
      }
    }
  ],
  "sessionEntityTypes":[
    {
      "name":"projects/project-id/agent/sessions/session-id/entityTypes/fruit",
      "entities":[
        {
          "value":"APPLE_KEY",
          "synonyms":[
            "apple",
            "green apple",
            "crabapple"
          ]
        },
        {
          "value":"ORANGE_KEY",
          "synonyms":[
            "orange"
          ]
        }
      ],
      "entityOverrideMode":"ENTITY_OVERRIDE_MODE_OVERRIDE"
    }
  ]
}

Habilita y administra la entrega

Si deseas habilitar y administrar la entrega para el agente con la consola, sigue estos pasos:

  1. Ve a la consola de Dialogflow ES.
  2. Selecciona un agente.
  3. Selecciona Entrega en el menú de la barra lateral izquierda.
  4. Con el botón para activar o desactivar, selecciona Enabled (Habilitado) en el campo Webhook.
  5. Proporciona los detalles del servicio de webhook en el formulario. Si el webhook no requiere autenticación, deja los campos de autenticación en blanco.
  6. Haz clic en Guardar en la parte inferior de la página.

Captura de pantalla de la habilitación de la entrega.

Para habilitar y administrar la entrega del agente con la API, consulta la referencia del agente. Se pueden usar los métodos getFulfillment y updateFulfillment para administrar la configuración de las entregas.

Si deseas habilitar la entrega para un intent con la consola, haz lo siguiente:

  1. En el menú de la barra lateral izquierda, selecciona Intents.
  2. Selecciona un intent.
  3. Desplázate hacia abajo hasta la sección Entrega.
  4. Activa la opción Enable webhook call for this intent.
  5. Haz clic en Guardar.

Si deseas habilitar la entrega para un intent con la API, consulta la referencia de los intents. Configura el campo webhookState como WEBHOOK_STATE_ENABLED.

Errores de webhook

Si el servicio webhook experimenta un error, debería mostrar uno de los siguientes códigos de estado HTTP:

  • 400 Solicitud incorrecta
  • 401 Sin autorización
  • 403 Prohibido
  • 404 No encontrado
  • 500 Falla del servidor
  • 503 Servicio no disponible

En cualquiera de las siguientes situaciones de error, Dialogflow responde al usuario final con la respuesta integrada configurada para el intent que coincide en la actualidad:

  • Se excedió el tiempo de espera de respuesta
  • Código de estado de error recibido
  • La respuesta no es válida
  • El servicio de webhook no está disponible

Además, si la coincidencia del intent se activó mediante una llamada a la API de intents de detección, el campo status en la respuesta de intents de detección contiene la información del error de webhook. Por ejemplo:

"status": {
    "code": 206,
    "message": "Webhook call failed. <details of the error...>"
}

Usa Cloud Functions

Existen varias formas de usar Cloud Functions para la entrega. El editor directo de Dialogflow se integra a Cloud Functions. Cuando usas el editor directo para crear y editar tu código de webhook, Dialogflow establece una conexión segura con tu función de Cloud Functions.

También puedes usar una función de Cloud Functions que no creó el editor directo (quizás porque quieres usar un lenguaje que no sea Node.js). Si la Cloud Function reside en el mismo proyecto que tu agente, tu agente puede llamar a tu webhook sin necesidad de realizar una configuración especial.

Sin embargo, hay dos situaciones en las que debes configurar manualmente esta integración:

  1. Debe existir la cuenta de servicio del Agente de servicio de Dialogflow con la siguiente dirección para tu proyecto de agente:
    service-agent-project-number@gcp-sa-dialogflow.iam.gserviceaccount.com
    Esta cuenta de servicio especial y la clave asociada se suelen crear automáticamente cuando creas el primer agente para un proyecto. Si tu agente se creó antes del 10 de mayo de 2021, es posible que debas activar la creación de esta cuenta de servicio especial con los siguientes elementos:
    1. Crea un agente nuevo para el proyecto.
    2. Ejecuta el siguiente comando:
      gcloud beta services identity create --service=dialogflow.googleapis.com --project=agent-project-id
  2. Si tu función de webhook reside en un proyecto diferente al agente, debes proporcionar la Función de IAM del Invocador de Cloud Functions a la cuenta de servicio del Agente de servicio de Dialogflow en el proyecto de la función.

Tokens de identidad del servicio

Cuando Dialogflow llama a un webhook, proporciona un token de identidad de Google con la solicitud. Cualquier webhook puede validar de manera opcional el token con bibliotecas cliente de Google o bibliotecas de código abierto como github.com/googleapis/google-auth-library-nodejs. Por ejemplo, puedes verificar el email del token de ID de la siguiente manera:

service-agent-project-number@gcp-sa-dialogflow.iam.gserviceaccount.com

Ejemplos

En los siguientes ejemplos, se muestra cómo recibir un WebhookRequest y enviar un WebhookResponse. En estos ejemplos, se hace referencia a intents creados en la guía de inicio rápido.

Comienza a usarlo

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type intent struct {
	DisplayName string `json:"displayName"`
}

type queryResult struct {
	Intent intent `json:"intent"`
}

type text struct {
	Text []string `json:"text"`
}

type message struct {
	Text text `json:"text"`
}

// webhookRequest is used to unmarshal a WebhookRequest JSON object. Note that
// not all members need to be defined--just those that you need to process.
// As an alternative, you could use the types provided by
// the Dialogflow protocol buffers:
// https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookRequest
type webhookRequest struct {
	Session     string      `json:"session"`
	ResponseID  string      `json:"responseId"`
	QueryResult queryResult `json:"queryResult"`
}

// webhookResponse is used to marshal a WebhookResponse JSON object. Note that
// not all members need to be defined--just those that you need to process.
// As an alternative, you could use the types provided by
// the Dialogflow protocol buffers:
// https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookResponse
type webhookResponse struct {
	FulfillmentMessages []message `json:"fulfillmentMessages"`
}

// welcome creates a response for the welcome intent.
func welcome(request webhookRequest) (webhookResponse, error) {
	response := webhookResponse{
		FulfillmentMessages: []message{
			{
				Text: text{
					Text: []string{"Welcome from Dialogflow Go Webhook"},
				},
			},
		},
	}
	return response, nil
}

// getAgentName creates a response for the get-agent-name intent.
func getAgentName(request webhookRequest) (webhookResponse, error) {
	response := webhookResponse{
		FulfillmentMessages: []message{
			{
				Text: text{
					Text: []string{"My name is Dialogflow Go Webhook"},
				},
			},
		},
	}
	return response, nil
}

// handleError handles internal errors.
func handleError(w http.ResponseWriter, err error) {
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprintf(w, "ERROR: %v", err)
}

// HandleWebhookRequest handles WebhookRequest and sends the WebhookResponse.
func HandleWebhookRequest(w http.ResponseWriter, r *http.Request) {
	var request webhookRequest
	var response webhookResponse
	var err error

	// Read input JSON
	if err = json.NewDecoder(r.Body).Decode(&request); err != nil {
		handleError(w, err)
		return
	}
	log.Printf("Request: %+v", request)

	// Call intent handler
	switch intent := request.QueryResult.Intent.DisplayName; intent {
	case "Default Welcome Intent":
		response, err = welcome(request)
	case "get-agent-name":
		response, err = getAgentName(request)
	default:
		err = fmt.Errorf("Unknown intent: %s", intent)
	}
	if err != nil {
		handleError(w, err)
		return
	}
	log.Printf("Response: %+v", response)

	// Send response
	if err = json.NewEncoder(w).Encode(&response); err != nil {
		handleError(w, err)
		return
	}
}

Java


// TODO: add GSON dependency to Pom file
// (https://mvnrepository.com/artifact/com.google.code.gson/gson/2.8.5)
// TODO: Uncomment the line bellow before running cloud function
// package com.example;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.BufferedWriter;

public class Example implements HttpFunction {

  public void service(HttpRequest request, HttpResponse response) throws Exception {
    JsonParser parser = new JsonParser();
    Gson gson = new GsonBuilder().create();

    JsonObject job = gson.fromJson(request.getReader(), JsonObject.class);
    String str =
        job.getAsJsonObject("queryResult")
            .getAsJsonObject("intent")
            .getAsJsonPrimitive("displayName")
            .toString();
    JsonObject o = null;
    String a = '"' + "Default Welcome Intent" + '"';
    String b = '"' + "get-agent-name" + '"';
    String responseText = "";

    if (str.equals(a)) {
      responseText = '"' + "Hello from a Java GCF Webhook" + '"';
    } else if (str.equals(b)) {
      responseText = '"' + "My name is Flowhook" + '"';
    } else {
      responseText = '"' + "Sorry I didn't get that" + '"';
    }

    o =
        parser
            .parse(
                "{\"fulfillmentMessages\": [ { \"text\": { \"text\": [ "
                    + responseText
                    + " ] } } ] }")
            .getAsJsonObject();

    BufferedWriter writer = response.getWriter();
    writer.write(o.toString());
  }
}

Node.js


// TODO: Add handleWebhook to 'Entry point' in the Google Cloud Function
exports.handleWebhook = (request, response) => {
  const tag = request.body.queryResult.intent.displayName;

  let jsonResponse = {};
  if (tag === 'Default Welcome Intent') {
    //fulfillment response to be sent to the agent if the request tag is equal to "welcome tag"
    jsonResponse = {
      fulfillment_messages: [
        {
          text: {
            //fulfillment text response to be sent to the agent
            text: ['Hello from a GCF Webhook'],
          },
        },
      ],
    };
  } else if (tag === 'get-name') {
    //fulfillment response to be sent to the agent if the request tag is equal to "welcome tag"
    jsonResponse = {
      fulfillment_messages: [
        {
          text: {
            //fulfillment text response to be sent to the agent
            text: ['My name is Flowhook'],
          },
        },
      ],
    };
  } else {
    jsonResponse = {
      //fulfillment text response to be sent to the agent if there are no defined responses for the specified tag
      fulfillment_messages: [
        {
          text: {
            ////fulfillment text response to be sent to the agent
            text: [
              `There are no fulfillment responses defined for "${tag}"" tag`,
            ],
          },
        },
      ],
    };
  }
  response.send(jsonResponse);
};

Python


# TODO: change the default Entry Point text to handleWebhook

def handleWebhook(request):

    req = request.get_json()

    responseText = ""
    intent = req["queryResult"]["intent"]["displayName"]

    if intent == "Default Welcome Intent":
        responseText = "Hello from a GCF Webhook"
    elif intent == "get-agent-name":
        responseText = "My name is Flowhook"
    else:
        responseText = f"There are no fulfillment responses defined for Intent {intent}"

    # You can also use the google.cloud.dialogflowcx_v3.types.WebhookRequest protos instead of manually writing the json object
    res = {"fulfillmentMessages": [{"text": {"text": [responseText]}}]}

    return res