Create a webhook service

The prebuilt agent you created in the last step requires a webhook . 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-telecommunications-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 
     cxtwh 
     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 
     fulfillmentInfo 
     struct 
     { 
     Tag 
     string 
     ` 
     json 
     : 
     "tag" 
     ` 
     } 
     type 
     sessionInfo 
     struct 
     { 
     Session 
     string 
     ` 
     json 
     : 
     "session" 
     ` 
     Parameters 
     map 
     [ 
     string 
     ] 
     interface 
     {} 
     ` 
     json 
     : 
     "parameters" 
     ` 
     } 
     type 
     text 
     struct 
     { 
     Text 
     [] 
     string 
     ` 
     json 
     : 
     "text" 
     ` 
     } 
     type 
     responseMessage 
     struct 
     { 
     Text 
     text 
     ` 
     json 
     : 
     "text" 
     ` 
     } 
     type 
     fulfillmentResponse 
     struct 
     { 
     Messages 
     [] 
     responseMessage 
     ` 
     json 
     : 
     "messages" 
     ` 
     } 
     // 
     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 
     : 
     // 
     pkg 
     . 
     go 
     . 
     dev 
     / 
     google 
     . 
     golang 
     . 
     org 
     / 
     genproto 
     / 
     googleapis 
     / 
     cloud 
     / 
     dialogflow 
     / 
     cx 
     / 
     v3 
     #WebhookRequest 
     type 
     webhookRequest 
     struct 
     { 
     FulfillmentInfo 
     fulfillmentInfo 
     ` 
     json 
     : 
     "fulfillmentInfo" 
     ` 
     SessionInfo 
     sessionInfo 
     ` 
     json 
     : 
     "sessionInfo" 
     ` 
     } 
     // 
     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 
     : 
     // 
     pkg 
     . 
     go 
     . 
     dev 
     / 
     google 
     . 
     golang 
     . 
     org 
     / 
     genproto 
     / 
     googleapis 
     / 
     cloud 
     / 
     dialogflow 
     / 
     cx 
     / 
     v3 
     #WebhookResponse 
     type 
     webhookResponse 
     struct 
     { 
     FulfillmentResponse 
     fulfillmentResponse 
     ` 
     json 
     : 
     "fulfillmentResponse" 
     ` 
     SessionInfo 
     sessionInfo 
     ` 
     json 
     : 
     "sessionInfo" 
     ` 
     } 
     // 
     detectCustomerAnomaly 
     handles 
     same 
     - 
     named 
     tag 
     . 
     func 
     detectCustomerAnomaly 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     { 
     // 
     Create 
     session 
     parameters 
     that 
     are 
     populated 
     in 
     the 
     response 
     . 
     // 
     This 
     example 
     hard 
     codes 
     values 
     , 
     but 
     a 
     real 
     system 
     // 
     might 
     look 
     up 
     this 
     value 
     in 
     a 
     database 
     . 
     p 
     := 
     map 
     [ 
     string 
     ] 
     interface 
     {}{ 
     "anomaly_detect" 
     : 
     "false" 
     , 
     "purchase" 
     : 
     "device protection" 
     , 
     "purchase_amount" 
     : 
     "12.25" 
     , 
     "bill_without_purchase" 
     : 
     "54.34" 
     , 
     "total_bill" 
     : 
     "66.59" 
     , 
     "first_month" 
     : 
     "January 1" 
     , 
     } 
     // 
     Build 
     and 
     return 
     the 
     response 
     . 
     response 
     := 
     webhookResponse 
     { 
     SessionInfo 
     : 
     sessionInfo 
     { 
     Parameters 
     : 
     p 
     , 
     }, 
     } 
     return 
     response 
     , 
     nil 
     } 
     // 
     validatePhoneLine 
     handles 
     same 
     - 
     named 
     tag 
     . 
     func 
     validatePhoneLine 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     { 
     // 
     Create 
     session 
     parameters 
     that 
     are 
     populated 
     in 
     the 
     response 
     . 
     // 
     This 
     example 
     hard 
     codes 
     values 
     , 
     but 
     a 
     real 
     system 
     // 
     might 
     look 
     up 
     this 
     value 
     in 
     a 
     database 
     . 
     p 
     := 
     map 
     [ 
     string 
     ] 
     interface 
     {}{ 
     "domestic_coverage" 
     : 
     "true" 
     , 
     "phone_line_verified" 
     : 
     "true" 
     , 
     } 
     // 
     Build 
     and 
     return 
     the 
     response 
     . 
     response 
     := 
     webhookResponse 
     { 
     SessionInfo 
     : 
     sessionInfo 
     { 
     Parameters 
     : 
     p 
     , 
     }, 
     } 
     return 
     response 
     , 
     nil 
     } 
     // 
     cruisePlanCoverage 
     handles 
     same 
     - 
     named 
     tag 
     . 
     func 
     cruisePlanCoverage 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     { 
     // 
     Get 
     the 
     existing 
     parameter 
     values 
     port 
     := 
     request 
     . 
     SessionInfo 
     . 
     Parameters 
     [ 
     "destination" 
     ] 
     . 
     ( 
     string 
     ) 
     port 
     = 
     strings 
     . 
     ToLower 
     ( 
     port 
     ) 
     // 
     Check 
     if 
     the 
     port 
     is 
     covered 
     covered 
     := 
     "false" 
     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 
     , 
     "Destinations" 
     , 
     spanner 
     . 
     Key 
     { 
     port 
     }, 
     [] 
     string 
     { 
     "Covered" 
     }) 
     if 
     err 
     != 
     nil 
     { 
     if 
     spanner 
     . 
     ErrCode 
     ( 
     err 
     ) 
     == 
     codes 
     . 
     NotFound 
     { 
     log 
     . 
     Printf 
     ( 
     "Port 
     %s 
     not found" 
     , 
     port 
     ) 
     } 
     else 
     { 
     return 
     webhookResponse 
     {}, 
     err 
     } 
     } 
     else 
     { 
     // 
     A 
     row 
     was 
     returned 
     , 
     so 
     check 
     the 
     value 
     var 
     c 
     bool 
     err 
     := 
     row 
     . 
     Column 
     ( 
     0 
     , 
    & c 
     ) 
     if 
     err 
     != 
     nil 
     { 
     return 
     webhookResponse 
     {}, 
     err 
     } 
     if 
     c 
     { 
     covered 
     = 
     "true" 
     } 
     } 
     } 
     else 
     { 
     // 
     No 
     Spanner 
     client 
     exists 
     , 
     so 
     use 
     hardcoded 
     list 
     of 
     ports 
     . 
     coveredPorts 
     := 
     map 
     [ 
     string 
     ] 
     bool 
     { 
     "anguilla" 
     : 
     true 
     , 
     "canada" 
     : 
     true 
     , 
     "mexico" 
     : 
     true 
     , 
     } 
     _ 
     , 
     ok 
     := 
     coveredPorts 
     [ 
     port 
     ] 
     if 
     ok 
     { 
     covered 
     = 
     "true" 
     } 
     } 
     // 
     Create 
     session 
     parameters 
     that 
     are 
     populated 
     in 
     the 
     response 
     . 
     // 
     This 
     example 
     hard 
     codes 
     values 
     , 
     but 
     a 
     real 
     system 
     // 
     might 
     look 
     up 
     this 
     value 
     in 
     a 
     database 
     . 
     p 
     := 
     map 
     [ 
     string 
     ] 
     interface 
     {}{ 
     "port_is_covered" 
     : 
     covered 
     , 
     } 
     // 
     Build 
     and 
     return 
     the 
     response 
     . 
     response 
     := 
     webhookResponse 
     { 
     SessionInfo 
     : 
     sessionInfo 
     { 
     Parameters 
     : 
     p 
     , 
     }, 
     } 
     return 
     response 
     , 
     nil 
     } 
     // 
     internationalCoverage 
     handles 
     same 
     - 
     named 
     tag 
     . 
     func 
     internationalCoverage 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     { 
     // 
     Get 
     the 
     existing 
     parameter 
     values 
     destination 
     := 
     request 
     . 
     SessionInfo 
     . 
     Parameters 
     [ 
     "destination" 
     ] 
     . 
     ( 
     string 
     ) 
     destination 
     = 
     strings 
     . 
     ToLower 
     ( 
     destination 
     ) 
     // 
     Hardcoded 
     list 
     of 
     covered 
     international 
     monthly 
     destinations 
     coveredMonthly 
     := 
     map 
     [ 
     string 
     ] 
     bool 
     { 
     "anguilla" 
     : 
     true 
     , 
     "australia" 
     : 
     true 
     , 
     "brazil" 
     : 
     true 
     , 
     "canada" 
     : 
     true 
     , 
     "chile" 
     : 
     true 
     , 
     "england" 
     : 
     true 
     , 
     "france" 
     : 
     true 
     , 
     "india" 
     : 
     true 
     , 
     "japan" 
     : 
     true 
     , 
     "mexico" 
     : 
     true 
     , 
     "singapore" 
     : 
     true 
     , 
     } 
     // 
     Hardcoded 
     list 
     of 
     covered 
     international 
     daily 
     destinations 
     coveredDaily 
     := 
     map 
     [ 
     string 
     ] 
     bool 
     { 
     "brazil" 
     : 
     true 
     , 
     "canada" 
     : 
     true 
     , 
     "chile" 
     : 
     true 
     , 
     "england" 
     : 
     true 
     , 
     "france" 
     : 
     true 
     , 
     "india" 
     : 
     true 
     , 
     "japan" 
     : 
     true 
     , 
     "mexico" 
     : 
     true 
     , 
     "singapore" 
     : 
     true 
     , 
     } 
     // 
     Check 
     coverage 
     coverage 
     := 
     "neither" 
     _ 
     , 
     monthly 
     := 
     coveredMonthly 
     [ 
     destination 
     ] 
     _ 
     , 
     daily 
     := 
     coveredDaily 
     [ 
     destination 
     ] 
     if 
     monthly 
    && daily 
     { 
     coverage 
     = 
     "both" 
     } 
     else 
     if 
     monthly 
     { 
     coverage 
     = 
     "monthly_only" 
     } 
     else 
     if 
     daily 
     { 
     coverage 
     = 
     "daily_only" 
     } 
     // 
     Create 
     session 
     parameters 
     that 
     are 
     populated 
     in 
     the 
     response 
     . 
     // 
     This 
     example 
     hard 
     codes 
     values 
     , 
     but 
     a 
     real 
     system 
     // 
     might 
     look 
     up 
     this 
     value 
     in 
     a 
     database 
     . 
     p 
     := 
     map 
     [ 
     string 
     ] 
     interface 
     {}{ 
     "coverage" 
     : 
     coverage 
     , 
     } 
     // 
     Build 
     and 
     return 
     the 
     response 
     . 
     response 
     := 
     webhookResponse 
     { 
     SessionInfo 
     : 
     sessionInfo 
     { 
     Parameters 
     : 
     p 
     , 
     }, 
     } 
     return 
     response 
     , 
     nil 
     } 
     // 
     cheapestPlan 
     handles 
     same 
     - 
     named 
     tag 
     . 
     func 
     cheapestPlan 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     { 
     // 
     Create 
     session 
     parameters 
     that 
     are 
     populated 
     in 
     the 
     response 
     . 
     // 
     This 
     example 
     hard 
     codes 
     values 
     , 
     but 
     a 
     real 
     system 
     // 
     might 
     look 
     up 
     this 
     value 
     in 
     a 
     database 
     . 
     p 
     := 
     map 
     [ 
     string 
     ] 
     interface 
     {}{ 
     "monthly_cost" 
     : 
     70 
     , 
     "daily_cost" 
     : 
     100 
     , 
     "suggested_plan" 
     : 
     "monthly" 
     , 
     } 
     // 
     Build 
     and 
     return 
     the 
     response 
     . 
     response 
     := 
     webhookResponse 
     { 
     SessionInfo 
     : 
     sessionInfo 
     { 
     Parameters 
     : 
     p 
     , 
     }, 
     } 
     return 
     response 
     , 
     nil 
     } 
     // 
     Define 
     a 
     type 
     for 
     handler 
     functions 
     . 
     type 
     handlerFn 
     func 
     ( 
     ctx 
     context 
     . 
     Context 
     , 
     request 
     webhookRequest 
     ) 
     ( 
     webhookResponse 
     , 
     error 
     ) 
     // 
     Create 
     a 
     map 
     from 
      
     tag 
     to 
     handler 
     function 
     . 
     var 
     handlers 
     map 
     [ 
     string 
     ] 
     handlerFn 
     = 
     map 
     [ 
     string 
     ] 
     handlerFn 
     { 
     "detectCustomerAnomaly" 
     : 
     detectCustomerAnomaly 
     , 
     "validatePhoneLine" 
     : 
     validatePhoneLine 
     , 
     "cruisePlanCoverage" 
     : 
     cruisePlanCoverage 
     , 
     "internationalCoverage" 
     : 
     internationalCoverage 
     , 
     "cheapestPlan" 
     : 
     cheapestPlan 
     , 
     } 
     // 
     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 
     tag 
     from 
      
     the 
     request 
     , 
     and 
     call 
     the 
     corresponding 
     // 
     function 
     that 
     handles 
     that 
     tag 
     . 
     tag 
     := 
     request 
     . 
     FulfillmentInfo 
     . 
     Tag 
     if 
     fn 
     , 
     ok 
     := 
     handlers 
     [ 
     tag 
     ]; 
     ok 
     { 
     response 
     , 
     err 
     = 
     fn 
     ( 
     r 
     . 
     Context 
     (), 
     request 
     ) 
     } 
     else 
     { 
     err 
     = 
     fmt 
     . 
     Errorf 
     ( 
     "Unknown tag: 
     %s 
     " 
     , 
     tag 
     ) 
     } 
     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 will associate this webhook with your agent. To configure the webhook for your agent:

  1. Open the Dialogflow CX console .
  2. Choose your Google Cloud project.
  3. Select your agent.
  4. Select the Managetab.
  5. Click Webhooks.
  6. Click the existing webhook (created by the prebuilt agent).
  7. Update the Webhook URLto the webhook URL you saved when creating the function.
  8. Set the Subtypeto Standard.
  9. 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 Hi! I'm the virtual telecommunications agent. I can help you resolve billing and plan questions, troubleshoot, and add travel and cruise plans. How can I assist you today?
2
I want to add a cruise plan Let me take a look for you. What is your phone number with us?
3
123-123-1234 How many days is your cruise?
4
7 Option 1 includes talk and text. It costs $[XX]/day. More info here [URL]. Option 2 includes talk, text and data. It costs $[XX]/day. More info here [URL]. Which cruise plan would you like to add?
5
Option 1 Got it. I've added that plan to your account. While on your cruise, you may also need coverage on land. What port will you be stopping at?
6
Mexico And, how many days are you spending there?
7
3 Great news! Mexico has free coverage on land. You're all set. Is there anything else I can help you with today?

This particular conversation was managed by the Cruise Plan flow.

At conversational turn #6, you provided "Mexico" as your destination port. The destination port and the number of days there are captured as the destination and trip_duration form parameters of the Collect Port page. Browse your agent to find these parameter definitions.

Screenshot of Collect Port page in Dialogflow counsole

In the Collect Port page, there is a condition route for form completion: $page.params.status = "FINAL" . Once the two form parameters have been provided, this route is called. This route calls your webhook and provides the cruisePlanCoverage tag to your webhook. If you examine the webhook code above, you see that this tag triggers the same named function to be called.

This function determines whether the provided destination is covered by the plan. 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 list of destinations. In upcoming steps, you will alter the environment for the function so that it retrieves data from a database in order to validate plan coverage for destinations.

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:

Design a Mobile Site
View Site in Mobile | Classic
Share by: