Stay organized with collections
Save and categorize content based on your preferences.
The prebuilt agent you created in the last step
cannot provide dynamic data like account balances,
because everything is hardcoded into the agent.
In this step of the tutorial,
you will create a
webhook
that can provide dynamic data to the agent.
Cloud Run functions
are used to host the webhook in this tutorial due to their simplicity,
but there are many other ways that you could host a webhook service.
The example also uses the Go programming language,
but you can use any
language supported by Cloud Run functions.
Create the Function
Cloud Run functions can be created with the Google Cloud console (visit documentation, open console).
To create a function for this tutorial:
It is important that your Dialogflow agent and the function
are both in the same project.
This is the easiest way for Dialogflow to have
secure access to your function.
Before creating the function,
select your project from the Google Cloud console.
Click Create Function, and set the following fields:
Environment: 1st gen
Function name: tutorial-banking-webhook
Region: If you specified a region for your agent,
use the same region.
HTTP Trigger type: HTTP
URL: Click the copy button here and save the value.
You will need this URL when configuring the webhook.
Authentication: Require authentication
Require HTTPS: checked
Click Save.
Click Next (You do not need special runtime, build,
connections, or security settings).
Set the following fields:
Runtime: Select the latest Go runtime.
Source code: Inline Editor
Entry point: HandleWebhookRequest
Replace the code with the following:
packageestwhimport("context""encoding/json""fmt""log""net/http""os""strings""cloud.google.com/go/spanner""google.golang.org/grpc/codes")//clientisaSpannerclient,createdonlyoncetoavoidcreation//foreveryrequest.//See:https://cloud.google.com/functions/docs/concepts/go-runtime#one-time_initializationvarclient*spanner.Clientfuncinit(){//Ifusingadatabase,theseenvironmentvariableswillbeset.pid:=os.Getenv("PROJECT_ID")iid:=os.Getenv("SPANNER_INSTANCE_ID")did:=os.Getenv("SPANNER_DATABASE_ID")ifpid!="" && iid!="" && did!=""{db:=fmt.Sprintf("projects/%s/instances/%s/databases/%s",pid,iid,did)log.Printf("Creating Spanner client for %s",db)varerrerror//Usethebackgroundcontextwhencreatingtheclient,//butusetherequestcontextforcallstotheclient.//See:https://cloud.google.com/functions/docs/concepts/go-runtime#contextcontextclient,err=spanner.NewClient(context.Background(),db)iferr!=nil{log.Fatalf("spanner.NewClient: %v",err)}}}typequeryResultstruct{Actionstring`json:"action"`Parametersmap[string]interface{}`json:"parameters"`}typetextstruct{Text[]string`json:"text"`}typemessagestruct{Texttext`json:"text"`}//webhookRequestisusedtounmarshalaWebhookRequestJSONobject.Notethat//notallmembersneedtobedefined--justthosethatyouneedtoprocess.//Asanalternative,youcouldusethetypesprovidedby//theDialogflowprotocolbuffers://https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookRequesttypewebhookRequeststruct{Sessionstring`json:"session"`ResponseIDstring`json:"responseId"`QueryResultqueryResult`json:"queryResult"`}//webhookResponseisusedtomarshalaWebhookResponseJSONobject.Notethat//notallmembersneedtobedefined--justthosethatyouneedtoprocess.//Asanalternative,youcouldusethetypesprovidedby//theDialogflowprotocolbuffers://https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookResponsetypewebhookResponsestruct{FulfillmentMessages[]message`json:"fulfillmentMessages"`}//accountBalanceCheckhandlesthesimilarnamedactionfuncaccountBalanceCheck(ctxcontext.Context,requestwebhookRequest)(webhookResponse,error){account:=request.QueryResult.Parameters["account"].(string)account=strings.ToLower(account)vartablestringifaccount=="savings account"{table="Savings"}else{table="Checking"}s:="Your balance is $0"ifclient!=nil{//ASpannerclientexists,soaccessthedatabase.//See:https://pkg.go.dev/cloud.google.com/go/spanner#ReadOnlyTransaction.ReadRowrow,err:=client.Single().ReadRow(ctx,table,spanner.Key{1},//TheaccountID[]string{"Balance"})iferr!=nil{ifspanner.ErrCode(err)==codes.NotFound{log.Printf("Account %d not found",1)}else{returnwebhookResponse{},err}}else{//Arowwasreturned,socheckthevaluevarbalanceint64err:=row.Column(0, &balance)iferr!=nil{returnwebhookResponse{},err}s=fmt.Sprintf("Your balance is $%.2f",float64(balance)/100.0)}}response:=webhookResponse{FulfillmentMessages:[]message{{Text:text{Text:[]string{s},},},},}returnresponse,nil}//Defineatypeforhandlerfunctions.typehandlerFnfunc(ctxcontext.Context,requestwebhookRequest)(webhookResponse,error)//Createamapfromactiontohandlerfunction.varhandlersmap[string]handlerFn=map[string]handlerFn{"account.balance.check":accountBalanceCheck,}//handleErrorhandlesinternalerrors.funchandleError(whttp.ResponseWriter,errerror){log.Printf("ERROR: %v",err)http.Error(w,fmt.Sprintf("ERROR: %v",err),http.StatusInternalServerError)}//HandleWebhookRequesthandlesWebhookRequestandsendstheWebhookResponse.funcHandleWebhookRequest(whttp.ResponseWriter,r*http.Request){varrequestwebhookRequestvarresponsewebhookResponsevarerrerror//ReadinputJSONiferr=json.NewDecoder(r.Body).Decode(&request);err!=nil{handleError(w,err)return}log.Printf("Request: %+v",request)//Gettheactionfromtherequest,andcallthecorresponding//functionthathandlesthataction.action:=request.QueryResult.Actioniffn,ok:=handlers[action];ok{response,err=fn(r.Context(),request)}else{err=fmt.Errorf("Unknown action: %s",action)}iferr!=nil{handleError(w,err)return}log.Printf("Response: %+v",response)//Sendresponseiferr=json.NewEncoder(w).Encode(&response);err!=nil{handleError(w,err)return}}
Click Deploy.
Wait until the status indicator shows that the function
has successfully deployed.
While waiting, examine the code you just deployed.
Configure the webhook for your agent
Now that the webhook exists as a service,
you need to associate this webhook with your agent.
This is done via fulfillment.
To enable and manage fulfillment for your agent:
Provide the URL that you copied from above.
Leave all other fields blank.
Click Save at the bottom of the page.
Now that fulfillment is enabled for the agent,
you need to enable fulfillment for an intent:
Select Intents in the left sidebar menu.
Select the account.balance.check intent.
Scroll down to the Fulfillment section.
Toggle Enable webhook call for this intent to on.
Click Save.
Try the agent
Your agent is now ready to try.
Click the Test Agent button to open the simulator.
Attempt to have the following conversation with the agent:
Conversational turn
You
Agent
1
Hello
Hello, thanks for choosing ACME Bank.
2
I want to know my account balance
What account do you want the balance for: savings or checking?
3
Checking
Here's your latest balance: $0.00
At conversational turn #3,
you supplied "checking" as the account type.
The account.balance.check intent has a parameter called account.
This parameter is set to "checking" in this conversation.
The intent also has an action value of "account.balance.check".
The webhook service is called,
and it is passed the parameter and action values.
If you examine the webhook code above,
you see that this action triggers a similar named function to be called.
The function determines the account balance.
The function checks whether specific environment variables are set
with information for connecting to the database.
If these environment variables are not set,
the function uses a hardcoded account balance.
In upcoming steps,
you will alter the environment for the function
so that it retrieves data from a database.
Troubleshooting
The webhook code includes logging statements.
If you are having issues, try viewing the logs for your function.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Hard to understand","hardToUnderstand","thumb-down"],["Incorrect information or sample code","incorrectInformationOrSampleCode","thumb-down"],["Missing the information/samples I need","missingTheInformationSamplesINeed","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-08-25 UTC."],[[["\u003cp\u003eThis tutorial guides you through creating a webhook using Cloud Run functions to provide dynamic data, such as account balances, to a Dialogflow agent, enhancing its capabilities beyond hardcoded responses.\u003c/p\u003e\n"],["\u003cp\u003eThe webhook, hosted via Cloud Run functions, interacts with the Dialogflow agent through fulfillment, and this interaction is triggered by specific intents, in this example, the 'account.balance.check' intent.\u003c/p\u003e\n"],["\u003cp\u003eThe example uses the Go programming language to develop the webhook code, which can be viewed and edited inline within the Google Cloud console, and handles requests and responses between Dialogflow and a potential database.\u003c/p\u003e\n"],["\u003cp\u003eTo set up the webhook, you must first create a Cloud Run function within the same Google Cloud project as your Dialogflow agent, ensuring secure access, then configure fulfillment within Dialogflow, associating it with the created function's URL.\u003c/p\u003e\n"],["\u003cp\u003eAfter enabling the webhook for specific intents and configuring fulfillment, you can test the enhanced Dialogflow agent using the simulator, where the agent can now retrieve and display dynamic account balance information.\u003c/p\u003e\n"]]],[],null,["# Create a webhook service\n\nThe prebuilt agent you created in the last step\ncannot provide dynamic data like account balances,\nbecause everything is hardcoded into the agent.\nIn this step of the tutorial,\nyou will create a\n[webhook](/dialogflow/es/docs/fulfillment-overview)\nthat can provide dynamic data to the agent.\n[Cloud Run functions](/functions/docs)\nare used to host the webhook in this tutorial due to their simplicity,\nbut there are many other ways that you could host a webhook service.\nThe example also uses the Go programming language,\nbut you can use any\n[language supported by Cloud Run functions](/functions/docs/concepts/exec).\n\nCreate the Function\n-------------------\n\nCloud Run functions can be created with the Google Cloud console ([visit documentation](https://support.google.com/cloud/answer/3465889?ref_topic=3340599), [open console](https://console.cloud.google.com/)).\nTo create a function for this tutorial:\n\n1. It is important that your Dialogflow agent and the function\n are both in the same project.\n This is the easiest way for Dialogflow to have\n [secure access to your function](/dialogflow/es/docs/fulfillment-webhook#gcf).\n Before creating the function,\n select your project from the Google Cloud console.\n\n [Go to project selector](https://console.cloud.google.com/projectselector2/home/dashboard)\n2. Open the Cloud Run functions overview page.\n\n [Go to Cloud Run functions overview](https://console.cloud.google.com/functions/list)\n3. Click **Create Function**, and set the following fields:\n\n - **Environment**: 1st gen\n - **Function name**: tutorial-banking-webhook\n - **Region**: If you specified a region for your agent, use the same region.\n - **HTTP Trigger type**: HTTP\n - **URL**: Click the copy button here and save the value. You will need this URL when configuring the webhook.\n - **Authentication**: Require authentication\n - **Require HTTPS**: checked\n4. Click **Save**.\n\n5. Click **Next** (You do not need special runtime, build,\n connections, or security settings).\n\n6. Set the following fields:\n\n - **Runtime**: Select the latest Go runtime.\n - **Source code**: Inline Editor\n - **Entry point**: HandleWebhookRequest\n7. Replace the code with the following:\n\n ```python\n package estwh\n\n import (\n \t\"context\"\n \t\"encoding/json\"\n \t\"fmt\"\n \t\"log\"\n \t\"net/http\"\n \t\"os\"\n \t\"strings\"\n\n \t\"cloud.google.com/go/spanner\"\n \"google.golang.org/grpc/codes\"\n )\n\n // client is a Spanner client, created only once to avoid creation\n // for every request.\n // See: https://cloud.google.com/functions/docs/concepts/go-runtime#one-time_initialization\n var client *spanner.Client\n\n func init() {\n \t// If using a database, these environment variables will be set.\n \tpid := os.Getenv(\"PROJECT_ID\")\n \tiid := os.Getenv(\"SPANNER_INSTANCE_ID\")\n \tdid := os.Getenv(\"SPANNER_DATABASE_ID\")\n \tif pid != \"\" && iid != \"\" && did != \"\" {\n \t\tdb := fmt.Sprintf(\"projects/%s/instances/%s/databases/%s\",\n \t\t\tpid, iid, did)\n \t\tlog.Printf(\"Creating Spanner client for %s\", db)\n \t\tvar err error\n \t\t// Use the background context when creating the client,\n \t\t// but use the request context for calls to the client.\n \t\t// See: https://cloud.google.com/functions/docs/concepts/go-runtime#contextcontext\n \t\tclient, err = spanner.NewClient(context.Background(), db)\n \t\tif err != nil {\n \t\t\tlog.Fatalf(\"spanner.NewClient: %v\", err)\n \t\t}\n \t}\n }\n\n type queryResult struct {\n \tAction string `json:\"action\"`\n \tParameters map[string]interface{} `json:\"parameters\"`\n }\n\n type text struct {\n \tText []string `json:\"text\"`\n }\n\n type message struct {\n \tText text `json:\"text\"`\n }\n\n // webhookRequest is used to unmarshal a WebhookRequest JSON object. Note that\n // not all members need to be defined--just those that you need to process.\n // As an alternative, you could use the types provided by\n // the Dialogflow protocol buffers:\n // https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookRequest\n type webhookRequest struct {\n \tSession string `json:\"session\"`\n \tResponseID string `json:\"responseId\"`\n \tQueryResult queryResult `json:\"queryResult\"`\n }\n\n // webhookResponse is used to marshal a WebhookResponse JSON object. Note that\n // not all members need to be defined--just those that you need to process.\n // As an alternative, you could use the types provided by\n // the Dialogflow protocol buffers:\n // https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookResponse\n type webhookResponse struct {\n \tFulfillmentMessages []message `json:\"fulfillmentMessages\"`\n }\n\n // accountBalanceCheck handles the similar named action\n func accountBalanceCheck(ctx context.Context, request webhookRequest) (\n \twebhookResponse, error) {\n \taccount := request.QueryResult.Parameters[\"account\"].(string)\n \taccount = strings.ToLower(account)\n \tvar table string\n \tif account == \"savings account\" {\n \t\ttable = \"Savings\"\n \t} else {\n \t\ttable = \"Checking\"\n \t}\n \ts := \"Your balance is $0\"\n \tif client != nil {\n \t\t// A Spanner client exists, so access the database.\n \t\t// See: https://pkg.go.dev/cloud.google.com/go/spanner#ReadOnlyTransaction.ReadRow\n \t\trow, err := client.Single().ReadRow(ctx,\n \t\t\ttable,\n \t\t\tspanner.Key{1}, // The account ID\n \t\t\t[]string{\"Balance\"})\n \t\tif err != nil {\n \t\t\tif spanner.ErrCode(err) == codes.NotFound {\n \t\t\t\tlog.Printf(\"Account %d not found\", 1)\n \t\t\t} else {\n \t\t\t\treturn webhookResponse{}, err\n \t\t\t}\n \t\t} else {\n \t\t\t// A row was returned, so check the value\n \t\t\tvar balance int64\n \t\t\terr := row.Column(0, &balance)\n \t\t\tif err != nil {\n \t\t\t\treturn webhookResponse{}, err\n \t\t\t}\n \t\t\ts = fmt.Sprintf(\"Your balance is $%.2f\", float64(balance)/100.0)\n \t\t}\n \t}\n \tresponse := webhookResponse{\n \t\tFulfillmentMessages: []message{\n \t\t\t{\n \t\t\t\tText: text{\n \t\t\t\t\tText: []string{s},\n \t\t\t\t},\n \t\t\t},\n \t\t},\n \t}\n \treturn response, nil\n }\n\n // Define a type for handler functions.\n type handlerFn func(ctx context.Context, request webhookRequest) (\n \twebhookResponse, error)\n\n // Create a map from action to handler function.\n var handlers map[string]handlerFn = map[string]handlerFn{\n \t\"account.balance.check\": accountBalanceCheck,\n }\n\n // handleError handles internal errors.\n func handleError(w http.ResponseWriter, err error) {\n \tlog.Printf(\"ERROR: %v\", err)\n \thttp.Error(w,\n \t\tfmt.Sprintf(\"ERROR: %v\", err),\n \t\thttp.StatusInternalServerError)\n }\n\n // HandleWebhookRequest handles WebhookRequest and sends the WebhookResponse.\n func HandleWebhookRequest(w http.ResponseWriter, r *http.Request) {\n \tvar request webhookRequest\n \tvar response webhookResponse\n \tvar err error\n\n \t// Read input JSON\n \tif err = json.NewDecoder(r.Body).Decode(&request); err != nil {\n \t\thandleError(w, err)\n \t\treturn\n \t}\n \tlog.Printf(\"Request: %+v\", request)\n\n \t// Get the action from the request, and call the corresponding\n \t// function that handles that action.\n \taction := request.QueryResult.Action\n \tif fn, ok := handlers[action]; ok {\n \t\tresponse, err = fn(r.Context(), request)\n \t} else {\n \t\terr = fmt.Errorf(\"Unknown action: %s\", action)\n \t}\n \tif err != nil {\n \t\thandleError(w, err)\n \t\treturn\n \t}\n \tlog.Printf(\"Response: %+v\", response)\n\n \t// Send response\n \tif err = json.NewEncoder(w).Encode(&response); err != nil {\n \t\thandleError(w, err)\n \t\treturn\n \t}\n }\n ```\n\n \u003cbr /\u003e\n\n8. Click **Deploy**.\n\n9. Wait until the status indicator shows that the function\n has successfully deployed.\n While waiting, examine the code you just deployed.\n\nConfigure the webhook for your agent\n------------------------------------\n\nNow that the webhook exists as a service,\nyou need to associate this webhook with your agent.\nThis is done via fulfillment.\nTo enable and manage fulfillment for your agent:\n\n1. Go to the [Dialogflow ES console](https://dialogflow.cloud.google.com).\n2. Select the pre-built agent you just created.\n3. Select **Fulfillment** in the left sidebar menu.\n4. Toggle the **Webhook** field to **Enabled**.\n5. Provide the URL that you copied from above. Leave all other fields blank.\n6. Click **Save** at the bottom of the page.\n\nNow that fulfillment is enabled for the agent,\nyou need to enable fulfillment for an intent:\n\n1. Select **Intents** in the left sidebar menu.\n2. Select the **account.balance.check** intent.\n3. Scroll down to the **Fulfillment** section.\n4. Toggle **Enable webhook call for this intent** to on.\n5. Click **Save**.\n\nTry the agent\n-------------\n\nYour agent is now ready to try.\nClick the **Test Agent** button to open the simulator.\nAttempt to have the following conversation with the agent:\n\nAt conversational turn #3,\nyou supplied \"checking\" as the account type.\nThe **account.balance.check** intent has a parameter called **account**.\nThis parameter is set to \"checking\" in this conversation.\nThe intent also has an action value of \"account.balance.check\".\nThe webhook service is called,\nand it is passed the parameter and action values.\n\nIf you examine the webhook code above,\nyou see that this action triggers a similar named function to be called.\nThe function determines the account balance.\nThe function checks whether specific environment variables are set\nwith information for connecting to the database.\nIf these environment variables are not set,\nthe function uses a hardcoded account balance.\nIn upcoming steps,\nyou will alter the environment for the function\nso that it retrieves data from a database.\n\nTroubleshooting\n---------------\n\nThe webhook code includes logging statements.\nIf you are having issues, try viewing the logs for your function.\n\nMore information\n----------------\n\nFor more information about the steps above, see:\n\n- [Cloud Run functions Go quickstart](/functions/docs/console-quickstart-1st-gen)"]]