Create a webhook service

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:

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

    Go to project selector

  2. Open the Cloud Run functions overview page.

    Go to Cloud Run functions overview

  3. 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
  4. Click Save.

  5. Click Next(You do not need special runtime, build, connections, or security settings).

  6. Set the following fields:

    • Runtime: Select the latest Go runtime.
    • Source code: Inline Editor
    • Entry point: HandleWebhookRequest
  7. Replace the code with the following:

     package 
     estwh 
     import 
      
     ( 
     "context" 
     "encoding/json" 
     "fmt" 
     "log" 
     "net/http" 
     "os" 
     "strings" 
     "cloud.google.com/go/spanner" 
     "google.golang.org/grpc/codes" 
     ) 
     // 
     client 
     is 
     a 
     Spanner 
     client 
     , 
     created 
     only 
     once 
     to 
     avoid 
     creation 
     // 
     for 
     every 
     request 
     . 
     // 
     See 
     : 
     https 
     : 
     // 
     cloud 
     . 
     google 
     . 
     com 
     / 
     functions 
     / 
     docs 
     / 
     concepts 
     / 
     go 
     - 
     runtime 
     #one-time_initialization 
     var 
     client 
     * 
     spanner 
     . 
     Client 
     func 
     init 
     () 
     { 
     // 
     If 
     using 
     a 
     database 
     , 
     these 
     environment 
     variables 
     will 
     be 
     set 
     . 
     pid 
     := 
     os 
     . 
     Getenv 
     ( 
     "PROJECT_ID" 
     ) 
     iid 
     := 
     os 
     . 
     Getenv 
     ( 
     "SPANNER_INSTANCE_ID" 
     ) 
     did 
     := 
     os 
     . 
     Getenv 
     ( 
     "SPANNER_DATABASE_ID" 
     ) 
     if 
     pid 
     != 
     "" 
    && iid 
     != 
     "" 
    && did 
     != 
     "" 
     { 
     db 
     := 
     fmt 
     . 
     Sprintf 
     ( 
     "projects/ 
     %s 
     /instances/ 
     %s 
     /databases/ 
     %s 
     " 
     , 
     pid 
     , 
     iid 
     , 
     did 
     ) 
     log 
     . 
     Printf 
     ( 
     "Creating Spanner client for 
     %s 
     " 
     , 
     db 
     ) 
     var 
     err 
     error 
     // 
     Use 
     the 
     background 
     context 
     when 
     creating 
     the 
     client 
     , 
     // 
     but 
     use 
     the 
     request 
     context 
     for 
     calls 
     to 
     the 
     client 
     . 
     // 
     See 
     : 
     https 
     : 
     // 
     cloud 
     . 
     google 
     . 
     com 
     / 
     functions 
     / 
     docs 
     / 
     concepts 
     / 
     go 
     - 
     runtime 
     #contextcontext 
     client 
     , 
     err 
     = 
     spanner 
     . 
     NewClient 
     ( 
     context 
     . 
     Background 
     (), 
     db 
     ) 
     if 
     err 
     != 
     nil 
     { 
     log 
     . 
     Fatalf 
     ( 
     "spanner.NewClient: %v" 
     , 
     err 
     ) 
     } 
     } 
     } 
     type 
     queryResult 
     struct 
     { 
     Action 
     string 
     ` 
     json 
     : 
     "action" 
     ` 
     Parameters 
     map 
     [ 
     string 
     ] 
     interface 
     {} 
     ` 
     json 
     : 
     "parameters" 
     ` 
     } 
     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" 
     ` 
     } 
     // 
     accountBalanceCheck 
     handles 
     the 
     similar 
     named 
     action 
     func 
     accountBalanceCheck 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     { 
     account 
     := 
     request 
     . 
     QueryResult 
     . 
     Parameters 
     [ 
     "account" 
     ] 
     . 
     ( 
     string 
     ) 
     account 
     = 
     strings 
     . 
     ToLower 
     ( 
     account 
     ) 
     var 
     table 
     string 
     if 
     account 
     == 
     "savings account" 
     { 
     table 
     = 
     "Savings" 
     } 
     else 
     { 
     table 
     = 
     "Checking" 
     } 
     s 
     := 
     "Your balance is $0" 
     if 
     client 
     != 
     nil 
     { 
     // 
     A 
     Spanner 
     client 
     exists 
     , 
     so 
     access 
     the 
     database 
     . 
     // 
     See 
     : 
     https 
     : 
     // 
     pkg 
     . 
     go 
     . 
     dev 
     / 
     cloud 
     . 
     google 
     . 
     com 
     / 
     go 
     / 
     spanner 
     #ReadOnlyTransaction.ReadRow 
     row 
     , 
     err 
     := 
     client 
     . 
     Single 
     () 
     . 
     ReadRow 
     ( 
     ctx 
     , 
     table 
     , 
     spanner 
     . 
     Key 
     { 
     1 
     }, 
     // 
     The 
     account 
     ID 
     [] 
     string 
     { 
     "Balance" 
     }) 
     if 
     err 
     != 
     nil 
     { 
     if 
     spanner 
     . 
     ErrCode 
     ( 
     err 
     ) 
     == 
     codes 
     . 
     NotFound 
     { 
     log 
     . 
     Printf 
     ( 
     "Account 
     %d 
     not found" 
     , 
     1 
     ) 
     } 
     else 
     { 
     return 
     webhookResponse 
     {}, 
     err 
     } 
     } 
     else 
     { 
     // 
     A 
     row 
     was 
     returned 
     , 
     so 
     check 
     the 
     value 
     var 
     balance 
     int64 
     err 
     := 
     row 
     . 
     Column 
     ( 
     0 
     , 
    & balance 
     ) 
     if 
     err 
     != 
     nil 
     { 
     return 
     webhookResponse 
     {}, 
     err 
     } 
     s 
     = 
     fmt 
     . 
     Sprintf 
     ( 
     "Your balance is $ 
     %.2f 
     " 
     , 
     float64 
     ( 
     balance 
     ) 
     / 
     100.0 
     ) 
     } 
     } 
     response 
     := 
     webhookResponse 
     { 
     FulfillmentMessages 
     : 
     [] 
     message 
     { 
     { 
     Text 
     : 
     text 
     { 
     Text 
     : 
     [] 
     string 
     { 
     s 
     }, 
     }, 
     }, 
     }, 
     } 
     return 
     response 
     , 
     nil 
     } 
     // 
     Define 
     a 
     type 
     for 
     handler 
     functions 
     . 
     type 
     handlerFn 
     func 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     // 
     Create 
     a 
     map 
     from 
      
     action 
     to 
     handler 
     function 
     . 
     var 
     handlers 
     map 
     [ 
     string 
     ] 
     handlerFn 
     = 
     map 
     [ 
     string 
     ] 
     handlerFn 
     { 
     "account.balance.check" 
     : 
     accountBalanceCheck 
     , 
     } 
     // 
     handleError 
     handles 
     internal 
     errors 
     . 
     func 
     handleError 
     ( 
     w 
     http 
     . 
     ResponseWriter 
     , 
     err 
     error 
     ) 
     { 
     log 
     . 
     Printf 
     ( 
     "ERROR: %v" 
     , 
     err 
     ) 
     http 
     . 
     Error 
     ( 
     w 
     , 
     fmt 
     . 
     Sprintf 
     ( 
     "ERROR: %v" 
     , 
     err 
     ), 
     http 
     . 
     StatusInternalServerError 
     ) 
     } 
     // 
     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 
     ) 
     // 
     Get 
     the 
     action 
     from 
      
     the 
     request 
     , 
     and 
     call 
     the 
     corresponding 
     // 
     function 
     that 
     handles 
     that 
     action 
     . 
     action 
     := 
     request 
     . 
     QueryResult 
     . 
     Action 
     if 
     fn 
     , 
     ok 
     := 
     handlers 
     [ 
     action 
     ]; 
     ok 
     { 
     response 
     , 
     err 
     = 
     fn 
     ( 
     r 
     . 
     Context 
     (), 
     request 
     ) 
     } 
     else 
     { 
     err 
     = 
     fmt 
     . 
     Errorf 
     ( 
     "Unknown action: 
     %s 
     " 
     , 
     action 
     ) 
     } 
     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 
     } 
     } 
    
  8. Click Deploy.

  9. 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:

  1. Go to the Dialogflow ES console .
  2. Select the pre-built agent you just created.
  3. Select Fulfillmentin the left sidebar menu.
  4. Toggle the Webhookfield to Enabled.
  5. Provide the URL that you copied from above. Leave all other fields blank.
  6. Click Saveat the bottom of the page.

Screenshot of enabling fulfillment.

Now that fulfillment is enabled for the agent, you need to enable fulfillment for an intent:

  1. Select Intentsin the left sidebar menu.
  2. Select the account.balance.checkintent.
  3. Scroll down to the Fulfillmentsection.
  4. Toggle Enable webhook call for this intentto on.
  5. Click Save.

Try the agent

Your agent is now ready to try. Click the Test Agentbutton 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.checkintent 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.

More information

For more information about the steps above, see:

Create a Mobile Website
View Site in Mobile | Classic
Share by: