How to do phone verification in Go with Twilio Verify

July 26, 2022
Written by

Phone verification in Go with Twilio Verify

Phone verification is an essential part of the user onboarding process: whenever you collect new contact methods from users you should make sure those are valid. You can also use a similar workflow to authenticate users on an ongoing basis with a one-time passcode (OTP) sent to the user's phone. This is a user-friendly way to do either primary or two-factor authentication (2FA).

This blog post will show you how to send an SMS OTP in Go using Twilio's Verify API. Best of all, once you implement the code you can send an OTP via WhatsApp, voice calls, and emails with one parameter change.

This post uses the Twilio Go Helper Library which is currently in Pilot and under active development. If you identify any issues, please open an issue on GitHub. Learn more about support for Pilot products.

Prerequisites for sending an SMS OTP

To follow along with this blog post you'll need:

  1. A Twilio Account. Sign up or sign in.
  2. A Twilio Verify Service. Create a Verify Service in the console.
  3. Go. Follow instructions in the documentation to download and install on your machine. Verify your install by running go version in your terminal. I'm on version go1.18.4.

Create a new Golang project to send one-time passcodes

Using your command prompt, navigate to your home directory. For new Go installations run:

Linux and Mac:

cd

Windows:

cd %HOMEPATH%

Create a new folder for your verification project and change into the project folder by running the following commands; I named mine verify-go:

mkdir verify-go
cd verify-go

Initialize a new module by running the following command. You can name it whatever you want, but I went with verify:

go mod init verify

Download the Twilio Go Helper Library:

go get github.com/twilio/twilio-go

Finally, create a new file where you can start writing code! I named mine verify.go. Add the following code to set up the imports and instantiate the Twilio client:

package main

import (
   "fmt"
   "os"

   "github.com/twilio/twilio-go"
   openapi "github.com/twilio/twilio-go/rest/verify/v2"
)

var TWILIO_ACCOUNT_SID string = os.Getenv("TWILIO_ACCOUNT_SID")
var TWILIO_AUTH_TOKEN string = os.Getenv("TWILIO_AUTH_TOKEN")
var VERIFY_SERVICE_SID string = os.Getenv("VERIFY_SERVICE_SID")
var client *twilio.RestClient = twilio.NewRestClientWithParams(twilio.ClientParams{
   Username: TWILIO_ACCOUNT_SID,
   Password: TWILIO_AUTH_TOKEN,
})

Set your TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and VERIFY_SERVICE_SID as environment variables using the respective details from your Twilio account. You can find these in the Twilio Console.

Never share these credentials on GitHub or other publicly accessible services.

Send the OTP

Next, add the following function to the bottom of verify.go. This code will send the OTP by calling the Verification endpoint. For this example, "sms" is hard coded as the channel but you could make this dynamic to accept "call", "whatsapp" or "email"! Learn more about channel options in the documentation.

func sendOtp(to string) {
   params := &openapi.CreateVerificationParams{}
   params.SetTo(to)
   params.SetChannel("sms")

   resp, err := client.VerifyV2.CreateVerification(VERIFY_SERVICE_SID, params)

   if err != nil {
       fmt.Println(err.Error())
   } else {
       fmt.Printf("Sent verification '%s'\n", *resp.Sid)
   }
}

Check the OTP

Next add the checkOtp() function below to the end of verify.go, which checks the OTP that is sent to the user. The function uses the command prompt and Go's fmt.Scanln to accept user input as a simple interface. In production you would build an OTP input modal or page in your site interface to accept the input.

func checkOtp(to string) {
   var code string
   fmt.Println("Please check your phone and enter the code:")
   fmt.Scanln(&code)

   params := &openapi.CreateVerificationCheckParams{}
   params.SetTo(to)
   params.SetCode(code)

   resp, err := client.VerifyV2.CreateVerificationCheck(VERIFY_SERVICE_SID, params)

   if err != nil {
       fmt.Println(err.Error())
   } else if *resp.Status == "approved" {
       fmt.Println("Correct!")
   } else {
       fmt.Println("Incorrect!")
   }
}

This code waits for user input to get the passcode which calls the Verification Check endpoint via the CreateVerificationCheck() function. The code then checks to make sure that the status is approved. If the user provides an incorrect code, the status will remain "pending".

Run the code to test the verification lifecycle

Finally, add a main() function at the end of verify.go to bring everything together. Replace your phone number in E.164 format to test it out.

func main() {
   to := "<your phone number here>"

   sendOtp(to)
   checkOtp(to)
}

Back in the command prompt run go run . to execute the code. You should see output like this:

$ go run .
Sent verification 'VEd123455403fa12345c4812345c812345'
Please check your phone and enter the code:
077100
Correct!

Next steps for account security

Now that you've sent an SMS OTP, try changing the Channel parameter to send the OTP via WhatsApp, a voice call, or email! If you want to go beyond OTPs, check out the Verify APIs for Authenticator Apps like Authy or Push Authentication.

For more account security best practices, check out:

I can't wait to see what you build and secure!