Back to Blog

The easiest way to capture and validate an address

If you need to ship or pickup an item for a customer you might need their address. But how do you verify if it exists?

Address information can consist out of street, house number, postal code, po box, city, state, country, etc. Using Flow.ai and the Google Maps Geocoding API, there is no need to ask all this information separately.

Design your flow and capturing the address

Training the AI to contextually extract house numbers and cities using entities works, but it requires a lot of training data.

Even adding a hundred annotated examples might not give you a 100% result. In stead, it is far easier to use an Any text slot of the entity type Text.

Using the Any text you can capture any user input and use that within a code action to geo-code it to structured data.

Validating the address with the Google API

There are different providers, but for this example we’ll show how to use the Google Maps Geocoding API to convert user input into structured address information.

To use the Geocoding API you need to get an API key. The API key is a unique identifier that is used to authenticate requests with Google for usage and billing purposes.

Writing the code action

The first step is getting the address information. Any text works the same way as any other params.

In the code example below, we simply receive the address and output it to the logging console.

async payload => {
  const {
    params,
    user
  } = payload

  const { address } = params

  const lastAddress = address[address.length - 1].value

  console.info('The last address captured', lastAddress)
}

Now that we have the address we can validate and transform it into structured address data using the Google API.

async payload => {
  try {
    const {
      params,
      user
    } = payload

    const { address } = params

    const lastAddress = address[address.length - 1].value

    // For this example we show the API key in code
    // but best practice is to use Configuration
    // https://flow.ai/docs/actions/code_configuration
    const key='MY SECRET API KEY'

    // Construct a Google API URL
    const url=encodeURI(`https://maps.googleapis.com/maps/api/geocode/json?address=${lastAddress}&key=${key}&language=${user.profile.locale || 'en'}`)

    // Call the API
    const { data } = await request(url)
    const { status, results } = data

    console.info('The API converted the address to', results)
  } catch(err) {
    console.error('Failed to call the API', err)
  }
}

Below is a sample geocoding result, in JSON. Please check the Google documentation to find out about all the details of the response.

[
  {
      "address_components" : [
        {
            "long_name" : "1600",
            "short_name" : "1600",
            "types" : [ "street_number" ]
        },
        {
            "long_name" : "Amphitheatre Pkwy",
            "short_name" : "Amphitheatre Pkwy",
            "types" : [ "route" ]
        },
        {
            "long_name" : "Mountain View",
            "short_name" : "Mountain View",
            "types" : [ "locality", "political" ]
        },
        {
            "long_name" : "Santa Clara County",
            "short_name" : "Santa Clara County",
            "types" : [ "administrative_area_level_2", "political" ]
        },
        {
            "long_name" : "California",
            "short_name" : "CA",
            "types" : [ "administrative_area_level_1", "political" ]
        },
        {
            "long_name" : "United States",
            "short_name" : "US",
            "types" : [ "country", "political" ]
        },
        {
            "long_name" : "94043",
            "short_name" : "94043",
            "types" : [ "postal_code" ]
        }
      ],
      "formatted_address" : "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA",
      "geometry" : {
        "location" : {
            "lat" : 37.4224764,
            "lng" : -122.0842499
        },
        "location_type" : "ROOFTOP",
        "viewport" : {
            "northeast" : {
              "lat" : 37.4238253802915,
              "lng" : -122.0829009197085
            },
            "southwest" : {
              "lat" : 37.4211274197085,
              "lng" : -122.0855988802915
            }
        }
      },
      "place_id" : "ChIJ2eUgeAK6j4ARbn5u_wAGqWA",
      "types" : [ "street_address" ]
  }
]

Covering any edge cases

The Google API might return multiple results, so it’s good to confirm the returned address with the user.

For this we extend the flows to handle edge cases like:

Below is an example what your final flow could look like.

Within the code action we send a Carousel component to display the results, and ask the user to verify the address.

async payload => {
  
  try {  
    const {
      params,
      user
    } = payload

    const { address } = params

    const lastAddress = address[address.length - 1].value

    // Add the API key to the configuration
    const key = await toolbelt.config.get('api_key')

    // Build url
    const url=encodeURI(`https://maps.googleapis.com/maps/api/geocode/json?address=${lastAddress}&key=${key}&language=${user.profile.locale || 'en'}`)

    const { data } = await request(url)
    const { status, results } = data
    if(status !== 'OK') {
      return trigger('INVALID_ADDRESS')
    }

    /*
     * Simple helper function to find a piece
     * of address information within the API results
     */
    const findAddressPart = (result, part) => {
      const component = result.address_components.find(c => c.types.indexOf(part) !== -1)
      if(component) {
        return component.short_name
      }

      return ""
    }

    // Create a carousel
    const carousel = new Carousel()

    for (let i = 0; i < results.length; i++) {
      const result = results[i];

      // Create a card
      const card = new Card({
        title: result.formatted_address
      })

      const params = [
        new Param('delivery_street', findAddressPart(result, 'route')),
        new Param('delivery_zipcode', findAddressPart(result, 'postal_code')),
        new Param('delivery_housenumber', findAddressPart(result, 'street_number')),
        new Param('delivery_city', findAddressPart(result, 'locality')),
        new Param('delivery_country', findAddressPart(result, 'country'))
      ]

      card.addButton(new Button({
        label: "Confirm",
        type: 'event',
        value: 'ADDRESS_CONFIRMED',
        param: params
      }))

      carousel.addCard(card)
    }

    carousel.addQuickReply(new QuickReply({
      type: 'event',
      label: 'Try again',
      value: 'DELIVERY_ADDRESS'
    }))

    return new Message('We do not support audio only').addResponse(carousel)

  } catch(err) {
    console.error('Failed to call the API', err)
    trigger('INVALID_ADDRESS')
  }
}

As you can see you can really create advanced use cases.

Read more

Start for freeContact us